File: Provisioning\Internal\IProvisioningServices.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 System.Text.Json.Nodes;
 
using Azure;
using Azure.Core;
using Azure.ResourceManager;
using Azure.ResourceManager.Authorization;
using Azure.ResourceManager.Authorization.Models;
using Azure.ResourceManager.Resources;
using Azure.ResourceManager.Resources.Models;
using Azure.Security.KeyVault.Secrets;
 
namespace Aspire.Hosting.Azure.Provisioning.Internal;
 
/// <summary>
/// Provides access to Azure ARM client functionality.
/// </summary>
internal interface IArmClientProvider
{
    /// <summary>
    /// Gets the ARM client for Azure resource management.
    /// </summary>
    IArmClient GetArmClient(TokenCredential credential, string subscriptionId);
 
    /// <summary>
    /// Gets the ARM client for Azure resource management without a specific subscription.
    /// </summary>
    IArmClient GetArmClient(TokenCredential credential);
}
 
/// <summary>
/// Provides access to Azure Key Vault secret client functionality.
/// </summary>
internal interface ISecretClientProvider
{
    /// <summary>
    /// Gets a secret client for the specified vault URI.
    /// </summary>
    SecretClient GetSecretClient(Uri vaultUri);
}
 
/// <summary>
/// Provides bicep CLI execution functionality.
/// </summary>
internal interface IBicepCompiler
{
    /// <summary>
    /// Compiles a bicep file to ARM template JSON.
    /// </summary>
    Task<string> CompileBicepToArmAsync(string bicepFilePath, CancellationToken cancellationToken = default);
}
 
/// <summary>
/// Provides provisioning context creation functionality.
/// </summary>
internal interface IProvisioningContextProvider
{
    /// <summary>
    /// Creates a provisioning context for Azure resource operations.
    /// </summary>
    /// <param name="cancellationToken">The cancellation token.</param>
    /// <returns>A provisioning context.</returns>
    Task<ProvisioningContext> CreateProvisioningContextAsync(CancellationToken cancellationToken = default);
}
 
/// <summary>
/// Provides interactive management of Azure provisioning options in run mode.
/// </summary>
internal interface IAzureProvisioningOptionsManager
{
    /// <summary>
    /// Ensures Azure provisioning options are available, optionally forcing the user to re-enter them.
    /// </summary>
    /// <param name="forcePrompt">Whether to force re-prompting even when options already exist.</param>
    /// <param name="cancellationToken">The cancellation token.</param>
    /// <returns><c>true</c> when options are available; otherwise, <c>false</c> if the interaction was canceled.</returns>
    Task<bool> EnsureProvisioningOptionsAsync(bool forcePrompt, CancellationToken cancellationToken = default);
 
    /// <summary>
    /// Gets the current in-memory provisioning options.
    /// </summary>
    /// <param name="cancellationToken">The cancellation token.</param>
    /// <returns>The current provisioning options.</returns>
    Task<AzureProvisioningOptionsState> GetProvisioningOptionsAsync(CancellationToken cancellationToken = default);
 
    /// <summary>
    /// Persists the current provisioning options to deployment state without creating a provisioning context.
    /// </summary>
    /// <param name="cancellationToken">The cancellation token.</param>
    Task PersistProvisioningOptionsAsync(CancellationToken cancellationToken = default);
 
    /// <summary>
    /// Applies provisioning option values and persists the resulting Azure context.
    /// </summary>
    /// <param name="options">The option values to apply.</param>
    /// <param name="cancellationToken">The cancellation token.</param>
    /// <returns>The persisted provisioning options.</returns>
    Task<AzureProvisioningOptionsState> ApplyProvisioningOptionsAsync(AzureProvisioningOptionsUpdate options, CancellationToken cancellationToken = default);
}
 
/// <summary>
/// Azure provisioning option values supplied by a command.
/// </summary>
internal sealed record AzureProvisioningOptionsUpdate(string? SubscriptionId, string? ResourceGroup, string? Location, string? TenantId);
 
/// <summary>
/// The currently persisted Azure provisioning options.
/// </summary>
internal sealed record AzureProvisioningOptionsState(string? SubscriptionId, string? ResourceGroup, string? Location, string? TenantId);
 
/// <summary>
/// No-op implementation used in publish mode where interactive provisioning options management is not needed.
/// </summary>
internal sealed class NoOpAzureProvisioningOptionsManager : IAzureProvisioningOptionsManager
{
    public Task<bool> EnsureProvisioningOptionsAsync(bool forcePrompt, CancellationToken cancellationToken = default) => Task.FromResult(false);
    public Task<AzureProvisioningOptionsState> GetProvisioningOptionsAsync(CancellationToken cancellationToken = default)
        => Task.FromResult(new AzureProvisioningOptionsState(null, null, null, null));
    public Task PersistProvisioningOptionsAsync(CancellationToken cancellationToken = default) => Task.CompletedTask;
    public Task<AzureProvisioningOptionsState> ApplyProvisioningOptionsAsync(AzureProvisioningOptionsUpdate options, CancellationToken cancellationToken = default)
        => Task.FromResult(new AzureProvisioningOptionsState(options.SubscriptionId, options.ResourceGroup, options.Location, options.TenantId));
}
 
/// <summary>
/// Abstraction for Azure ArmClient.
/// </summary>
internal interface IArmClient
{
    /// <summary>
    /// Gets the default subscription and its matching tenant.
    /// </summary>
    Task<(ISubscriptionResource subscription, ITenantResource tenant)> GetSubscriptionAndTenantAsync(CancellationToken cancellationToken = default);
 
    /// <summary>
    /// Gets all tenants accessible to the current user.
    /// </summary>
    Task<IEnumerable<ITenantResource>> GetAvailableTenantsAsync(CancellationToken cancellationToken = default);
 
    /// <summary>
    /// Gets all subscriptions accessible to the current user.
    /// </summary>
    Task<IEnumerable<ISubscriptionResource>> GetAvailableSubscriptionsAsync(CancellationToken cancellationToken = default);
 
    /// <summary>
    /// Gets all subscriptions accessible to the current user filtered by tenant ID.
    /// </summary>
    Task<IEnumerable<ISubscriptionResource>> GetAvailableSubscriptionsAsync(string? tenantId, CancellationToken cancellationToken = default);
 
    /// <summary>
    /// Gets a subscription by identifier.
    /// </summary>
    Task<ISubscriptionResource> GetSubscriptionAsync(string subscriptionId, CancellationToken cancellationToken = default);
 
    /// <summary>
    /// Gets all available locations for the specified subscription.
    /// </summary>
    Task<IEnumerable<(string Name, string DisplayName)>> GetAvailableLocationsAsync(string subscriptionId, CancellationToken cancellationToken = default);
 
    /// <summary>
    /// Gets detailed information about available resource groups including their locations.
    /// </summary>
    Task<IEnumerable<(string Name, string Location)>> GetAvailableResourceGroupsWithLocationAsync(string subscriptionId, CancellationToken cancellationToken = default);
 
    /// <summary>
    /// Gets Azure locations that support the specified resource type.
    /// </summary>
    Task<IEnumerable<string>> GetSupportedLocationsAsync(string subscriptionId, string resourceType, CancellationToken cancellationToken = default);
 
    /// <summary>
    /// Gets role assignments collection for the specified scope.
    /// </summary>
    IRoleAssignmentCollection GetRoleAssignments(ResourceIdentifier scope);
 
    /// <summary>
    /// Determines whether the specified Azure resource currently exists.
    /// </summary>
    Task<bool> ResourceExistsAsync(string resourceId, CancellationToken cancellationToken = default);
 
    /// <summary>
    /// Deletes the specified Azure resource.
    /// </summary>
    /// <param name="resourceId">The Azure resource ID to delete.</param>
    /// <param name="cancellationToken">The cancellation token.</param>
    Task DeleteResourceAsync(string resourceId, CancellationToken cancellationToken = default);
 
    /// <summary>
    /// Purges a soft-deleted Azure Key Vault tombstone in the specified location.
    /// </summary>
    /// <remarks>
    /// Key Vault soft-delete tombstones are location-scoped and can remain after the
    /// live vault resource no longer exists, so purge is modeled separately from delete.
    /// </remarks>
    /// <param name="resourceId">The Azure Key Vault resource ID whose deleted tombstone should be purged.</param>
    /// <param name="location">The Azure location that owns the deleted Key Vault tombstone.</param>
    /// <param name="cancellationToken">The cancellation token.</param>
    /// <returns><c>true</c> when a tombstone was found and purged; otherwise, <c>false</c>.</returns>
    Task<bool> PurgeDeletedKeyVaultAsync(string resourceId, string location, CancellationToken cancellationToken = default);
 
    /// <summary>
    /// Cancels the specified Azure deployment.
    /// </summary>
    Task CancelDeploymentAsync(string deploymentId, CancellationToken cancellationToken = default);
 
    /// <summary>
    /// Gets the specified Azure deployment, or <c>null</c> when it no longer exists.
    /// </summary>
    Task<AzureDeploymentState?> GetDeploymentAsync(string deploymentId, CancellationToken cancellationToken = default);
 
    /// <summary>
    /// Gets Azure resource IDs targeted by the specified deployment.
    /// </summary>
    IAsyncEnumerable<string> GetDeploymentTargetResourceIdsAsync(string deploymentId, CancellationToken cancellationToken = default);
 
    /// <summary>
    /// Gets Azure deployment operations for the specified deployment.
    /// </summary>
    IAsyncEnumerable<AzureDeploymentOperationDetails> GetDeploymentOperationsAsync(
        string deploymentId,
        bool recursive = true,
        CancellationToken cancellationToken = default);
}
 
/// <summary>
/// Abstraction for Azure SubscriptionResource.
/// </summary>
internal interface ISubscriptionResource
{
    /// <summary>
    /// Gets the subscription resource identifier.
    /// </summary>
    ResourceIdentifier Id { get; }
 
    /// <summary>
    /// Gets the subscription display name.
    /// </summary>
    string? DisplayName { get; }
 
    /// <summary>
    /// Gets the tenant ID.
    /// </summary>
    Guid? TenantId { get; }
 
    /// <summary>
    /// Gets resource groups collection.
    /// </summary>
    IResourceGroupCollection GetResourceGroups();
 
    /// <summary>
    /// Gets ARM deployments collection.
    /// </summary>
    IArmDeploymentCollection GetArmDeployments();
}
 
/// <summary>
/// Abstraction for Azure ResourceGroupCollection.
/// </summary>
internal interface IResourceGroupCollection
{
    /// <summary>
    /// Gets a resource group.
    /// </summary>
    Task<Response<IResourceGroupResource>> GetAsync(string resourceGroupName, CancellationToken cancellationToken = default);
 
    /// <summary>
    /// Creates or updates a resource group.
    /// </summary>
    Task<ArmOperation<IResourceGroupResource>> CreateOrUpdateAsync(WaitUntil waitUntil, string resourceGroupName, ResourceGroupData data, CancellationToken cancellationToken = default);
}
 
/// <summary>
/// Abstraction for Azure ResourceGroupResource.
/// </summary>
internal interface IResourceGroupResource
{
    /// <summary>
    /// Gets the resource group resource identifier.
    /// </summary>
    ResourceIdentifier Id { get; }
 
    /// <summary>
    /// Gets the resource group name.
    /// </summary>
    string Name { get; }
 
    /// <summary>
    /// Gets ARM deployments collection.
    /// </summary>
    IArmDeploymentCollection GetArmDeployments();
 
    /// <summary>
    /// Deletes the resource group.
    /// </summary>
    Task<ArmOperation> DeleteAsync(WaitUntil waitUntil, CancellationToken cancellationToken = default);
 
    /// <summary>
    /// Lists all resources in the resource group.
    /// </summary>
    /// <returns>A list of resources with their name and type.</returns>
    IAsyncEnumerable<(string Name, string ResourceType)> GetResourcesAsync(CancellationToken cancellationToken = default);
}
 
/// <summary>
/// Abstraction for Azure RoleAssignmentCollection.
/// </summary>
internal interface IRoleAssignmentCollection
{
    /// <summary>
    /// Creates or updates a role assignment.
    /// </summary>
    Task<ArmOperation<RoleAssignmentResource>> CreateOrUpdateAsync(
        WaitUntil waitUntil,
        string roleAssignmentName,
        RoleAssignmentCreateOrUpdateContent content,
        CancellationToken cancellationToken = default);
}
 
/// <summary>
/// Abstraction for Azure ArmDeploymentCollection.
/// </summary>
internal interface IArmDeploymentCollection
{
    /// <summary>
    /// Creates or updates a deployment.
    /// </summary>
    Task<ArmOperation<ArmDeploymentResource>> CreateOrUpdateAsync(
        WaitUntil waitUntil,
        string deploymentName,
        ArmDeploymentContent content,
        CancellationToken cancellationToken = default);
 
    /// <summary>
    /// Cancels a running deployment.
    /// </summary>
    Task CancelAsync(string deploymentName, CancellationToken cancellationToken = default);
}
 
/// <summary>
/// Captures the ARM deployment fields Aspire needs when reconciling cached deployment state.
/// </summary>
internal sealed record AzureDeploymentState(string ProvisioningState, JsonObject? Outputs);
 
/// <summary>
/// Abstraction for Azure TenantResource.
/// </summary>
internal interface ITenantResource
{
    /// <summary>
    /// Gets the tenant ID.
    /// </summary>
    Guid? TenantId { get; }
 
    /// <summary>
    /// Gets the display name.
    /// </summary>
    string? DisplayName { get; }
 
    /// <summary>
    /// Gets the default domain.
    /// </summary>
    string? DefaultDomain { get; }
 
    /// <summary>
    /// Gets ARM deployments collection.
    /// </summary>
    IArmDeploymentCollection GetArmDeployments();
}
 
/// <summary>
/// Provides Azure principal retrieval functionality.
/// </summary>
internal interface IAzurePrincipalProvider
{
    /// <summary>
    /// Gets the Azure principal that the running credential represents.
    /// </summary>
    Task<AzurePrincipal> GetPrincipalAsync(CancellationToken cancellationToken = default);
}