diff --git a/src/Aspire.Hosting.Azure.ContainerRegistry/AzureContainerRegistryHelpers.cs b/src/Aspire.Hosting.Azure.ContainerRegistry/AzureContainerRegistryHelpers.cs index d299cc64db1..8f448f4a89a 100644 --- a/src/Aspire.Hosting.Azure.ContainerRegistry/AzureContainerRegistryHelpers.cs +++ b/src/Aspire.Hosting.Azure.ContainerRegistry/AzureContainerRegistryHelpers.cs @@ -7,7 +7,6 @@ #pragma warning disable ASPIREAZURE001 using Aspire.Hosting.ApplicationModel; -using Aspire.Hosting.Azure.Provisioning.Internal; using Aspire.Hosting.Pipelines; using Microsoft.Extensions.DependencyInjection; diff --git a/src/Aspire.Hosting.Azure/ITokenCredentialProvider.cs b/src/Aspire.Hosting.Azure/ITokenCredentialProvider.cs new file mode 100644 index 00000000000..589bcbad747 --- /dev/null +++ b/src/Aspire.Hosting.Azure/ITokenCredentialProvider.cs @@ -0,0 +1,32 @@ +// 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; + +namespace Aspire.Hosting.Azure; + +/// +/// Provides access to the that Aspire uses +/// to authenticate against Azure when provisioning resources and calling Azure APIs. +/// +/// +/// +/// This service is registered as a singleton when Azure provisioning is enabled +/// (e.g., by AddAzureProvisioning). +/// +/// +/// Integrations and app host code can resolve this service from +/// to obtain a instance configured by Aspire's +/// Azure provisioning options (for example, the configured tenant id and credential source). +/// The concrete credential type returned by is an implementation +/// detail and may change between releases; callers should treat the value as an opaque +/// . +/// +/// +public interface ITokenCredentialProvider +{ + /// + /// Gets the to use for Azure authentication. + /// + TokenCredential TokenCredential { get; } +} diff --git a/src/Aspire.Hosting.Azure/Provisioning/Internal/IProvisioningServices.cs b/src/Aspire.Hosting.Azure/Provisioning/Internal/IProvisioningServices.cs index d04839558ee..36e1527f1cf 100644 --- a/src/Aspire.Hosting.Azure/Provisioning/Internal/IProvisioningServices.cs +++ b/src/Aspire.Hosting.Azure/Provisioning/Internal/IProvisioningServices.cs @@ -244,14 +244,3 @@ internal interface IUserPrincipalProvider /// Task GetUserPrincipalAsync(CancellationToken cancellationToken = default); } - -/// -/// Provides access to Azure token credentials. -/// -internal interface ITokenCredentialProvider -{ - /// - /// Gets the token credential for Azure authentication. - /// - TokenCredential TokenCredential { get; } -} diff --git a/src/Aspire.Hosting.Foundry/PromptAgent/AzurePromptAgentResource.cs b/src/Aspire.Hosting.Foundry/PromptAgent/AzurePromptAgentResource.cs index 0d78fae9685..9ce681ee63a 100644 --- a/src/Aspire.Hosting.Foundry/PromptAgent/AzurePromptAgentResource.cs +++ b/src/Aspire.Hosting.Foundry/PromptAgent/AzurePromptAgentResource.cs @@ -6,7 +6,6 @@ using System.Globalization; using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Azure; -using Aspire.Hosting.Azure.Provisioning.Internal; using Aspire.Hosting.Pipelines; using Aspire.Hosting.Publishing; using Azure.AI.Projects; diff --git a/tests/Aspire.Hosting.Azure.Tests/TokenCredentialProviderTests.cs b/tests/Aspire.Hosting.Azure.Tests/TokenCredentialProviderTests.cs new file mode 100644 index 00000000000..f0de536fe46 --- /dev/null +++ b/tests/Aspire.Hosting.Azure.Tests/TokenCredentialProviderTests.cs @@ -0,0 +1,79 @@ +// 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.Utils; +using Azure.Core; +using Microsoft.Extensions.DependencyInjection; + +namespace Aspire.Hosting.Azure.Tests; + +public class TokenCredentialProviderTests +{ + [Fact] + public void AddAzureProvisioning_RegistersITokenCredentialProvider() + { + using var builder = TestDistributedApplicationBuilder.Create(); + builder.AddAzureProvisioning(); + + using var app = builder.Build(); + + var provider = app.Services.GetRequiredService(); + + Assert.NotNull(provider); + Assert.NotNull(provider.TokenCredential); + } + + [Fact] + public void AddAzureProvisioning_RegistersITokenCredentialProviderAsSingleton() + { + using var builder = TestDistributedApplicationBuilder.Create(); + builder.AddAzureProvisioning(); + + using var app = builder.Build(); + + var first = app.Services.GetRequiredService(); + var second = app.Services.GetRequiredService(); + + Assert.Same(first, second); + Assert.Same(first.TokenCredential, second.TokenCredential); + } + + [Fact] + public void AddingAzureResource_RegistersITokenCredentialProvider() + { + // AddAzureProvisioning is invoked indirectly when an Azure resource is added. + using var builder = TestDistributedApplicationBuilder.Create(); + builder.AddAzureInfrastructure("infra", _ => { }); + + using var app = builder.Build(); + + var provider = app.Services.GetRequiredService(); + + Assert.NotNull(provider.TokenCredential); + } + + [Fact] + public void ITokenCredentialProvider_CanBeReplacedWithCustomImplementation() + { + // External callers should be able to plug in their own credential by + // replacing the registered service with their own implementation. + var customCredential = new TestTokenCredential(); + var customProvider = new CustomTokenCredentialProvider(customCredential); + + using var builder = TestDistributedApplicationBuilder.Create(); + builder.AddAzureProvisioning(); + builder.Services.AddSingleton(customProvider); + + using var app = builder.Build(); + + var resolved = app.Services.GetRequiredService(); + + Assert.Same(customProvider, resolved); + Assert.Same(customCredential, resolved.TokenCredential); + } + + private sealed class CustomTokenCredentialProvider(TokenCredential credential) : ITokenCredentialProvider + { + public TokenCredential TokenCredential { get; } = credential; + } +}