File: AzureApiManagementResource.cs
Web Access
Project: src\src\Aspire.Hosting.Azure.ApiManagement\Aspire.Hosting.Azure.ApiManagement.csproj (Aspire.Hosting.Azure.ApiManagement)
// 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 Azure.Provisioning.ApiManagement;
using Azure.Provisioning.Primitives;
 
namespace Aspire.Hosting.Azure;
 
/// <summary>
/// Represents an Azure API Management service.
/// </summary>
/// <remarks>
/// API Management is provisioned in Azure and acts as a gateway for APIs added with
/// <see cref="AzureApiManagementExtensions.AddApi{T}"/>.
/// </remarks>
/// <param name="name">The name of the Aspire resource.</param>
/// <param name="options">The API Management service options.</param>
/// <param name="configureInfrastructure">The callback that configures Azure infrastructure.</param>
[AspireExport]
public class AzureApiManagementResource(
    string name,
    AzureApiManagementOptions options,
    Action<AzureResourceInfrastructure> configureInfrastructure)
    : AzureProvisioningResource(name, configureInfrastructure), IAzurePrivateEndpointTarget
{
    private readonly List<string> _inboundPolicyStatements = [];
    private readonly List<RoleAssignmentAnnotation> _identityRoleAssignments = [];
 
    /// <summary>
    /// Gets the configured service options.
    /// </summary>
    public AzureApiManagementOptions Options { get; } = options ?? throw new ArgumentNullException(nameof(options));
 
    /// <summary>
    /// Gets the APIs provisioned in this API Management service.
    /// </summary>
    internal List<AzureApiManagementApiResource> Apis { get; } = [];
 
    /// <summary>
    /// Gets the backends provisioned in this API Management service.
    /// </summary>
    internal List<AzureApiManagementBackendResource> Backends { get; } = [];
 
    /// <summary>
    /// Gets the backend pools provisioned in this API Management service.
    /// </summary>
    internal List<AzureApiManagementBackendPoolResource> BackendPools { get; } = [];
 
    /// <summary>
    /// Gets the products provisioned in this API Management service.
    /// </summary>
    internal List<AzureApiManagementProductResource> Products { get; } = [];
 
    /// <summary>
    /// Gets the named values provisioned in this API Management service.
    /// </summary>
    internal List<AzureApiManagementNamedValueResource> NamedValues { get; } = [];
 
    /// <summary>
    /// Gets the policy fragments provisioned in this API Management service.
    /// </summary>
    internal List<AzureApiManagementPolicyFragmentResource> PolicyFragments { get; } = [];
 
    /// <summary>
    /// Gets the custom domains configured on this API Management service.
    /// </summary>
    internal List<AzureApiManagementCustomDomain> CustomDomains { get; } = [];
 
    /// <summary>
    /// Gets or sets the service-level Application Insights diagnostic.
    /// </summary>
    internal AzureApiManagementDiagnostic? Diagnostic { get; set; }
 
    /// <summary>
    /// Gets the physical service name output reference.
    /// </summary>
    [AspireExportIgnore(Reason = "Bicep output references are not projected to polyglot AppHosts.")]
    public BicepOutputReference NameOutputReference => new("name", this);
 
    /// <summary>
    /// Gets the resource ID output reference.
    /// </summary>
    [AspireExportIgnore(Reason = "Bicep output references are not projected to polyglot AppHosts.")]
    public BicepOutputReference Id => new("id", this);
 
    /// <summary>
    /// Gets the default gateway URL output reference.
    /// </summary>
    [AspireExportIgnore(Reason = "Bicep output references are not projected to polyglot AppHosts.")]
    public BicepOutputReference GatewayUrl => new("gatewayUrl", this);
 
    /// <summary>
    /// Gets the user-assigned managed identity used for backend authentication and Key Vault access.
    /// </summary>
    /// <remarks>
    /// New services attach this identity automatically. Existing services require an existing identity
    /// that is already attached outside Aspire. Supply it with
    /// <see cref="AzureApiManagementExtensions.WithAzureUserAssignedIdentity"/>.
    /// </remarks>
    [AspireExport]
    public AzureUserAssignedIdentityResource Identity { get; private set; } =
        new(AzureApiManagementExtensions.CreateBoundedIdentifier($"{name}-identity", 64));
 
    internal bool HasDefaultIdentity { get; private set; } = true;
 
    internal void SetIdentity(AzureUserAssignedIdentityResource identity)
    {
        // Move only APIM's inferred permissions; a supplied identity can be shared with other resources.
        foreach (var roleAssignment in _identityRoleAssignments)
        {
            Identity.Annotations.Remove(roleAssignment);
            identity.Annotations.Add(roleAssignment);
        }
 
        References.Remove(Identity);
        Identity = identity;
        HasDefaultIdentity = false;
        References.Add(identity);
    }
 
    internal void AddIdentityRoleAssignment(AzureProvisioningResource target, RoleDefinition role)
    {
        var annotation = new RoleAssignmentAnnotation(target, new HashSet<RoleDefinition> { role });
        _identityRoleAssignments.Add(annotation);
        Identity.Annotations.Add(annotation);
    }
 
    /// <summary>
    /// Gets the user-assigned managed identity principal ID output reference.
    /// </summary>
    [AspireExportIgnore(Reason = "Bicep output references are not projected to polyglot AppHosts.")]
    public BicepOutputReference PrincipalId => Identity.PrincipalId;
 
    /// <summary>
    /// Gets the complete service-level policy document, when one has been configured.
    /// </summary>
    internal string? PolicyXml { get; set; }
 
    /// <summary>
    /// Gets the ordered service-level inbound policy statements.
    /// </summary>
    internal IReadOnlyList<string> InboundPolicyStatements => _inboundPolicyStatements;
 
    /// <summary>
    /// Gets the classic virtual network configuration.
    /// </summary>
    internal AzureApiManagementVirtualNetworkConfiguration? VirtualNetworkConfiguration { get; set; }
 
    /// <summary>
    /// Adds an inbound policy statement.
    /// </summary>
    internal void AddInboundPolicyStatement(string policyXml) => _inboundPolicyStatements.Add(policyXml);
 
    /// <inheritdoc/>
    public override ProvisionableResource AddAsExistingResource(AzureResourceInfrastructure infrastructure)
    {
        var bicepIdentifier = this.GetBicepIdentifier();
        var existingService = infrastructure.GetProvisionableResources()
            .OfType<ApiManagementService>()
            .SingleOrDefault(resource => resource.BicepIdentifier == bicepIdentifier);
 
        if (existingService is not null)
        {
            return existingService;
        }
 
        var service = ApiManagementService.FromExisting(bicepIdentifier, "2025-03-01-preview");
        if (!TryApplyExistingResourceAnnotation(this, infrastructure, service))
        {
            service.Name = NameOutputReference.AsProvisioningParameter(infrastructure);
        }
        infrastructure.Add(service);
 
        return service;
    }
 
    IEnumerable<string> IAzurePrivateEndpointTarget.GetPrivateLinkGroupIds() => ["Gateway"];
 
    IEnumerable<string> IAzurePrivateEndpointTarget.GetPrivateDnsZoneNames() => ["privatelink.azure-api.net"];
}
 
internal sealed record AzureApiManagementVirtualNetworkConfiguration(
    AzureSubnetResource Subnet,
    AzureApiManagementVirtualNetworkMode Mode,
    AzureApiManagementVirtualNetworkKind Kind);
 
internal enum AzureApiManagementVirtualNetworkKind
{
    Classic,
    V2Integration,
    PremiumV2Injection,
}
 
internal sealed record AzureApiManagementSubnetUsageAnnotation(
    AzureApiManagementResource ApiManagement) : IResourceAnnotation;