Skip to content

Add CertificateClient and KeyClient support to Aspire.Azure.Security.KeyVault #8408

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 39 commits into from
May 6, 2025
Merged
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
29ee021
Builder pattern introduced for Secret, Key and Certificate clients
james-gould Mar 27, 2025
1863222
Migrated argument exceptions to keep pattern consistent
james-gould Mar 27, 2025
57865d0
Added base implementation of healthchecks to component library, feels…
james-gould Mar 28, 2025
d4f760c
Created an AbstractKeyVaultComponent to enhance reusability/testing
james-gould Mar 28, 2025
5b1206a
SecretClientExtensions documentation added
james-gould Mar 28, 2025
a3cd2a1
Full implementation and documentation completed for all 3 Client types
james-gould Mar 28, 2025
a9fe223
Merge branch 'main' into AdditionalClientSupport
james-gould Mar 28, 2025
a11ef4a
Creating multiple keyed clients of different types will now respect d…
james-gould Mar 29, 2025
fb11e58
Updated docs on AzureKeyVaultClientBuilder to provide an example of o…
james-gould Mar 29, 2025
70cc770
Conformance tests now added for all clients; old "catch all" tests re…
james-gould Mar 29, 2025
c2236bb
Tests created (and passing) - Certificate and Key missing from hosted…
james-gould Mar 29, 2025
4919723
Merge branch 'main' into AdditionalClientSupport
james-gould Mar 29, 2025
a1d8071
Updated README to include new API supported methods
james-gould Mar 29, 2025
8ef9254
Reintroduced original signature causing breaking change, added additi…
james-gould Apr 4, 2025
9dd500c
Merge branch 'dotnet:main' into AdditionalClientSupport
james-gould Apr 4, 2025
c53f6b6
Merge branch 'AdditionalClientSupport' of https://github.com/james-go…
james-gould Apr 4, 2025
1070dd1
Migrated AddAzureKeyVaultSecrets back to AspireKeyVaultExtensions
james-gould Apr 4, 2025
1fd06b1
Merge branch 'main' into AdditionalClientSupport
james-gould Apr 11, 2025
9fac8ee
Merge branch 'dotnet:main' into AdditionalClientSupport
james-gould Apr 14, 2025
df0d64a
Merge branch 'dotnet:main' into AdditionalClientSupport
james-gould Apr 15, 2025
71a1d57
Merge branch 'dotnet:main' into AdditionalClientSupport
james-gould Apr 18, 2025
88f9b21
Merge branch 'dotnet:main' into AdditionalClientSupport
james-gould Apr 22, 2025
a9fc82d
Merge branch 'dotnet:main' into AdditionalClientSupport
james-gould Apr 23, 2025
7679f1a
Merge branch 'dotnet:main' into AdditionalClientSupport
james-gould Apr 26, 2025
228b73f
Merge branch 'dotnet:main' into AdditionalClientSupport
james-gould Apr 29, 2025
6131630
Removed healthchecks for keys and certs
james-gould Apr 29, 2025
0b92db3
Fixed formatting
james-gould Apr 29, 2025
3837592
Project level surpression moved to source and healthcheck create defa…
james-gould Apr 29, 2025
6fe9df6
Remove unnecessary lambda
sebastienros Apr 29, 2025
5941be1
Removed builder pattern and added extensions for each Client type
james-gould Apr 29, 2025
4dcf0c9
Tests corrected after removal of builder pattern
james-gould Apr 29, 2025
5a5539b
Added context to IDE0200 warning disable
james-gould Apr 29, 2025
9e70672
Merge conflict resolved
james-gould Apr 29, 2025
951e331
Update src/Components/Aspire.Azure.Security.KeyVault/AbstractAzureKey…
james-gould Apr 29, 2025
2d5bd2f
Update src/Components/Aspire.Azure.Security.KeyVault/Aspire.Azure.Sec…
james-gould Apr 29, 2025
4c0d3e1
README updated with correct API after changes
james-gould Apr 29, 2025
315fc61
Merge branch 'AdditionalClientSupport' of https://github.com/james-go…
james-gould Apr 29, 2025
0a75d14
Merge branch 'dotnet:main' into AdditionalClientSupport
james-gould May 1, 2025
526e23d
Disable health checks for new components
sebastienros May 5, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -21,6 +21,8 @@
<PackageVersion Include="Azure.Search.Documents" Version="11.6.0" />
<PackageVersion Include="Azure.Messaging.WebPubSub" Version="1.4.0" />
<PackageVersion Include="Azure.Security.KeyVault.Secrets" Version="4.7.0" />
<PackageVersion Include="Azure.Security.KeyVault.Certificates" Version="4.7.0" />
<PackageVersion Include="Azure.Security.KeyVault.Keys" Version="4.7.0" />
<PackageVersion Include="Azure.Storage.Blobs" Version="12.24.0" />
<PackageVersion Include="Azure.Storage.Queues" Version="12.22.0" />
<PackageVersion Include="Microsoft.Azure.AppConfiguration.AspNetCore" Version="8.1.1" />
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;
using Aspire.Azure.Common;
using Azure.Core;
using Azure.Core.Extensions;
using Microsoft.Extensions.Azure;

namespace Aspire.Azure.Security.KeyVault;

/// <summary>
/// <para>Abstracts the common configuration binding required by <see cref="AzureComponent{TSettings, TClient, TClientOptions}"/></para>
/// <para>Deriving type implements KeyVaultClient specific item:</para>
/// <para><see cref="AzureComponent{TSettings, TClient, TClientOptions}.CreateHealthCheck(TClient, TSettings)"/></para>
/// </summary>
/// <typeparam name="TClient">The KeyVaultClient type for this component.</typeparam>
/// <typeparam name="TOptions">The associated configuration for the <typeparamref name="TClient"/></typeparam>
internal abstract class AbstractAzureKeyVaultComponent<TClient, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TOptions>
: AzureComponent<AzureSecurityKeyVaultSettings, TClient, TOptions>
where TClient : class
where TOptions : class
{
internal abstract TClient CreateComponentClient(Uri vaultUri, TOptions options, TokenCredential cred);

protected override IAzureClientBuilder<TClient, TOptions> AddClient(AzureClientFactoryBuilder azureFactoryBuilder, AzureSecurityKeyVaultSettings settings, string connectionName, string configurationSectionName)
{
return azureFactoryBuilder.AddClient<TClient, TOptions>((options, cred, _) =>
{
if (settings.VaultUri is null)
{
throw new InvalidOperationException($"VaultUri is missing. It should be provided in 'ConnectionStrings:{connectionName}' or under the 'VaultUri' key in the '{configurationSectionName}' configuration section.");
}

return CreateComponentClient(settings.VaultUri, options, cred);
});
}

protected override bool GetHealthCheckEnabled(AzureSecurityKeyVaultSettings settings)
=> !settings.DisableHealthChecks;

protected override TokenCredential? GetTokenCredential(AzureSecurityKeyVaultSettings settings)
=> settings.Credential;

protected override bool GetMetricsEnabled(AzureSecurityKeyVaultSettings settings)
=> false;

protected override bool GetTracingEnabled(AzureSecurityKeyVaultSettings settings)
=> !settings.DisableTracing;
}
Original file line number Diff line number Diff line change
@@ -19,6 +19,8 @@
<PackageReference Include="AspNetCore.HealthChecks.Azure.KeyVault.Secrets" />
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" />
<PackageReference Include="Azure.Security.KeyVault.Secrets" />
<PackageReference Include="Azure.Security.KeyVault.Certificates" />
<PackageReference Include="Azure.Security.KeyVault.Keys" />
<PackageReference Include="Microsoft.Extensions.Azure" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" />
Original file line number Diff line number Diff line change
@@ -3,16 +3,14 @@

using Aspire.Azure.Common;
using Aspire.Azure.Security.KeyVault;
using Azure.Core;
using Azure.Core.Extensions;
using Azure.Extensions.AspNetCore.Configuration.Secrets;
using Azure.Identity;
using Azure.Security.KeyVault.Certificates;
using Azure.Security.KeyVault.Keys;
using Azure.Security.KeyVault.Secrets;
using HealthChecks.Azure.KeyVault.Secrets;
using Microsoft.Extensions.Azure;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;

namespace Microsoft.Extensions.Hosting;

@@ -42,7 +40,8 @@ public static void AddAzureKeyVaultClient(
ArgumentNullException.ThrowIfNull(builder);
ArgumentException.ThrowIfNullOrEmpty(connectionName);

new KeyVaultComponent().AddClient(builder, DefaultConfigSectionName, configureSettings, configureClientBuilder, connectionName, serviceKey: null);
new AzureKeyVaultSecretsComponent()
.AddClient(builder, DefaultConfigSectionName, configureSettings, configureClientBuilder, connectionName, serviceKey: null);
}

/// <summary>
@@ -64,7 +63,96 @@ public static void AddKeyedAzureKeyVaultClient(
ArgumentNullException.ThrowIfNull(builder);
ArgumentException.ThrowIfNullOrEmpty(name);

new KeyVaultComponent().AddClient(builder, DefaultConfigSectionName, configureSettings, configureClientBuilder, connectionName: name, serviceKey: name);
new AzureKeyVaultSecretsComponent()
.AddClient(builder, DefaultConfigSectionName, configureSettings, configureClientBuilder, connectionName: name, serviceKey: name);
}

/// <summary>
/// Registers <see cref="CertificateClient"/> as a singleton in the services provided by the <paramref name="builder"/>.
/// </summary>
/// <param name="builder">The <see cref="IHostApplicationBuilder" /> to read config from and add services to.</param>
/// <param name="connectionName">A name used to retrieve the connection string from the ConnectionStrings configuration section.</param>
/// <param name="configureSettings">An optional method that can be used for customizing the <see cref="AzureSecurityKeyVaultSettings"/>. It's invoked after the settings are read from the configuration.</param>
/// <param name="configureClientBuilder">An optional method that can be used for customizing the <see cref="IAzureClientBuilder{TClient, TOptions}"/>.</param>
/// <remarks>Reads the configuration from "Aspire:Azure:Security:KeyVault:{name}" section.</remarks>
/// <exception cref="InvalidOperationException">Thrown when mandatory <see cref="AzureSecurityKeyVaultSettings.VaultUri"/> is not provided.</exception>
public static void AddAzureKeyVaultCertificateClient(
this IHostApplicationBuilder builder,
string connectionName,
Action<AzureSecurityKeyVaultSettings>? configureSettings = null,
Action<IAzureClientBuilder<CertificateClient, CertificateClientOptions>>? configureClientBuilder = null)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentException.ThrowIfNullOrEmpty(connectionName);

new AzureKeyVaultCertificatesComponent()
.AddClient(builder, DefaultConfigSectionName, configureSettings, configureClientBuilder, connectionName, serviceKey: null);
}

/// <summary>
/// Registers <see cref="CertificateClient"/> as a singleton for given <paramref name="name"/> in the services provided by the <paramref name="builder"/>.
/// </summary>
/// <param name="builder">The <see cref="IHostApplicationBuilder" /> to read config from and add services to.</param>
/// <param name="name">The name of the component, which is used as the <see cref="ServiceDescriptor.ServiceKey"/> of the service and also to retrieve the connection information from the ConnectionStrings configuration section.</param>
/// <param name="configureSettings">An optional method that can be used for customizing the <see cref="AzureSecurityKeyVaultSettings"/>. It's invoked after the settings are read from the configuration.</param>
/// <param name="configureClientBuilder">An optional method that can be used for customizing the <see cref="IAzureClientBuilder{TClient, TOptions}"/>.</param>
/// <remarks>Reads the configuration from "Aspire:Azure:Security:KeyVault:{name}" section.</remarks>
/// <exception cref="InvalidOperationException">Thrown when mandatory <see cref="AzureSecurityKeyVaultSettings.VaultUri"/> is not provided.</exception>
public static void AddKeyedAzureKeyVaultCertificateClient(
this IHostApplicationBuilder builder,
string name,
Action<AzureSecurityKeyVaultSettings>? configureSettings = null,
Action<IAzureClientBuilder<CertificateClient, CertificateClientOptions>>? configureClientBuilder = null)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentException.ThrowIfNullOrEmpty(name);

new AzureKeyVaultCertificatesComponent()
.AddClient(builder, DefaultConfigSectionName, configureSettings, configureClientBuilder, connectionName: name, serviceKey: name);
}

/// <summary>
/// Registers <see cref="KeyClient"/> as a singleton in the services provided by the <paramref name="builder"/>.
/// </summary>
/// <param name="builder">The <see cref="IHostApplicationBuilder" /> to read config from and add services to.</param>
/// <param name="connectionName">A name used to retrieve the connection string from the ConnectionStrings configuration section.</param>
/// <param name="configureSettings">An optional method that can be used for customizing the <see cref="AzureSecurityKeyVaultSettings"/>. It's invoked after the settings are read from the configuration.</param>
/// <param name="configureClientBuilder">An optional method that can be used for customizing the <see cref="IAzureClientBuilder{TClient, TOptions}"/>.</param>
/// <remarks>Reads the configuration from "Aspire:Azure:Security:KeyVault:{name}" section.</remarks>
/// <exception cref="InvalidOperationException">Thrown when mandatory <see cref="AzureSecurityKeyVaultSettings.VaultUri"/> is not provided.</exception>
public static void AddAzureKeyVaultKeyClient(
this IHostApplicationBuilder builder,
string connectionName,
Action<AzureSecurityKeyVaultSettings>? configureSettings = null,
Action<IAzureClientBuilder<KeyClient, KeyClientOptions>>? configureClientBuilder = null)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentException.ThrowIfNullOrEmpty(connectionName);

new AzureKeyVaultKeysComponent()
.AddClient(builder, DefaultConfigSectionName, configureSettings, configureClientBuilder, connectionName, serviceKey: null);
}

/// <summary>
/// Registers <see cref="KeyClient"/> as a singleton for given <paramref name="name"/> in the services provided by the <paramref name="builder"/>.
/// </summary>
/// <param name="builder">The <see cref="IHostApplicationBuilder" /> to read config from and add services to.</param>
/// <param name="name">The name of the component, which is used as the <see cref="ServiceDescriptor.ServiceKey"/> of the service and also to retrieve the connection information from the ConnectionStrings configuration section.</param>
/// <param name="configureSettings">An optional method that can be used for customizing the <see cref="AzureSecurityKeyVaultSettings"/>. It's invoked after the settings are read from the configuration.</param>
/// <param name="configureClientBuilder">An optional method that can be used for customizing the <see cref="IAzureClientBuilder{TClient, TOptions}"/>.</param>
/// <remarks>Reads the configuration from "Aspire:Azure:Security:KeyVault:{name}" section.</remarks>
/// <exception cref="InvalidOperationException">Thrown when mandatory <see cref="AzureSecurityKeyVaultSettings.VaultUri"/> is not provided.</exception>
public static void AddKeyedAzureKeyVaultKeyClient(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol - lots of "key"s in this method name.

this IHostApplicationBuilder builder,
string name,
Action<AzureSecurityKeyVaultSettings>? configureSettings = null,
Action<IAzureClientBuilder<KeyClient, KeyClientOptions>>? configureClientBuilder = null)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentException.ThrowIfNullOrEmpty(name);

new AzureKeyVaultKeysComponent()
.AddClient(builder, DefaultConfigSectionName, configureSettings, configureClientBuilder, connectionName: name, serviceKey: name);
}

/// <summary>
@@ -118,49 +206,4 @@ private static SecretClient GetSecretClient(

return new SecretClient(settings.VaultUri, settings.Credential ?? new DefaultAzureCredential(), clientOptions);
}

private sealed class KeyVaultComponent : AzureComponent<AzureSecurityKeyVaultSettings, SecretClient, SecretClientOptions>
{
protected override IAzureClientBuilder<SecretClient, SecretClientOptions> AddClient(
AzureClientFactoryBuilder azureFactoryBuilder, AzureSecurityKeyVaultSettings settings,
string connectionName, string configurationSectionName)
{
return azureFactoryBuilder.AddClient<SecretClient, SecretClientOptions>((options, cred, _) =>
{
if (settings.VaultUri is null)
{
throw new InvalidOperationException($"VaultUri is missing. It should be provided in 'ConnectionStrings:{connectionName}' or under the 'VaultUri' key in the '{configurationSectionName}' configuration section.");
}

return new SecretClient(settings.VaultUri, cred, options);
});
}

protected override IHealthCheck CreateHealthCheck(SecretClient client, AzureSecurityKeyVaultSettings settings)
=> new AzureKeyVaultSecretsHealthCheck(client, new AzureKeyVaultSecretsHealthCheckOptions());

protected override void BindClientOptionsToConfiguration(IAzureClientBuilder<SecretClient, SecretClientOptions> clientBuilder, IConfiguration configuration)
{
#pragma warning disable IDE0200 // Remove unnecessary lambda expression - needed so the ConfigBinder Source Generator works
clientBuilder.ConfigureOptions(options => configuration.Bind(options));
#pragma warning restore IDE0200
}

protected override void BindSettingsToConfiguration(AzureSecurityKeyVaultSettings settings, IConfiguration configuration)
{
configuration.Bind(settings);
}

protected override bool GetHealthCheckEnabled(AzureSecurityKeyVaultSettings settings)
=> !settings.DisableHealthChecks;

protected override TokenCredential? GetTokenCredential(AzureSecurityKeyVaultSettings settings)
=> settings.Credential;

protected override bool GetMetricsEnabled(AzureSecurityKeyVaultSettings settings)
=> false;

protected override bool GetTracingEnabled(AzureSecurityKeyVaultSettings settings)
=> !settings.DisableTracing;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Aspire.Azure.Common;
using Aspire.Azure.Security.KeyVault;
using Azure.Core;
using Azure.Core.Extensions;
using Azure.Security.KeyVault.Certificates;
using Microsoft.Extensions.Azure;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Diagnostics.HealthChecks;

namespace Microsoft.Extensions.Hosting;

/// <summary>
/// Representation of an <see cref="AzureComponent{TSettings, TClient, TClientOptions}"/> configured as a <see cref="CertificateClient"/>
/// </summary>
internal sealed class AzureKeyVaultCertificatesComponent : AbstractAzureKeyVaultComponent<CertificateClient, CertificateClientOptions>
{
internal override CertificateClient CreateComponentClient(Uri vaultUri, CertificateClientOptions options, TokenCredential cred)
=> new(vaultUri, cred, options);

protected override bool GetHealthCheckEnabled(AzureSecurityKeyVaultSettings settings)
=> false;

protected override IHealthCheck CreateHealthCheck(CertificateClient client, AzureSecurityKeyVaultSettings settings)
=> throw new NotImplementedException();

protected override void BindClientOptionsToConfiguration(IAzureClientBuilder<CertificateClient, CertificateClientOptions> clientBuilder, IConfiguration configuration)
#pragma warning disable IDE0200 // Remove unnecessary lambda expression - needed so the ConfigBinder Source Generator works
=> clientBuilder.ConfigureOptions(options => configuration.Bind(options));
#pragma warning restore IDE0200 // Remove unnecessary lambda expression

protected override void BindSettingsToConfiguration(AzureSecurityKeyVaultSettings settings, IConfiguration configuration)
=> configuration.Bind(settings);
}
Original file line number Diff line number Diff line change
@@ -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 Aspire.Azure.Security.KeyVault;
using Azure.Core;
using Azure.Core.Extensions;
using Azure.Security.KeyVault.Keys;
using Microsoft.Extensions.Azure;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Diagnostics.HealthChecks;

namespace Microsoft.Extensions.Hosting;

internal sealed class AzureKeyVaultKeysComponent : AbstractAzureKeyVaultComponent<KeyClient, KeyClientOptions>
{
protected override bool GetHealthCheckEnabled(AzureSecurityKeyVaultSettings settings)
=> false;

protected override IHealthCheck CreateHealthCheck(KeyClient client, AzureSecurityKeyVaultSettings settings)
=> throw new NotImplementedException();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this going to throw if settings.DisableHealthChecks == false?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@james-gould I think you need to add this is the two components that don't provide the health-check

protected override bool GetHealthCheckEnabled(AzureSecurityKeyVaultSettings settings)
{
    return false;
}

I will add it myself and approve the PR, unless you think it's wrong and we can then change it.


internal override KeyClient CreateComponentClient(Uri vaultUri, KeyClientOptions options, TokenCredential cred)
=> new(vaultUri, cred, options);

protected override void BindClientOptionsToConfiguration(IAzureClientBuilder<KeyClient, KeyClientOptions> clientBuilder, IConfiguration configuration)
#pragma warning disable IDE0200 // Remove unnecessary lambda expression - needed so the ConfigBinder Source Generator works
=> clientBuilder.ConfigureOptions(options => configuration.Bind(options));
#pragma warning restore IDE0200 // Remove unnecessary lambda expression

protected override void BindSettingsToConfiguration(AzureSecurityKeyVaultSettings settings, IConfiguration configuration)
=> configuration.Bind(settings);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Aspire.Azure.Security.KeyVault;
using Azure.Core;
using Azure.Core.Extensions;
using Azure.Security.KeyVault.Secrets;
using HealthChecks.Azure.KeyVault.Secrets;
using Microsoft.Extensions.Azure;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Diagnostics.HealthChecks;

namespace Microsoft.Extensions.Hosting;

internal sealed class AzureKeyVaultSecretsComponent : AbstractAzureKeyVaultComponent<SecretClient, SecretClientOptions>
{
protected override void BindClientOptionsToConfiguration(IAzureClientBuilder<SecretClient, SecretClientOptions> clientBuilder, IConfiguration configuration)
#pragma warning disable IDE0200 // Remove unnecessary lambda expression - needed so the ConfigBinder Source Generator works
=> clientBuilder.ConfigureOptions(options => configuration.Bind(options));
#pragma warning restore IDE0200 // Remove unnecessary lambda expression

protected override void BindSettingsToConfiguration(AzureSecurityKeyVaultSettings settings, IConfiguration configuration)
=> configuration.Bind(settings);

protected override IHealthCheck CreateHealthCheck(SecretClient client, AzureSecurityKeyVaultSettings settings)
=> new AzureKeyVaultSecretsHealthCheck(client, new AzureKeyVaultSecretsHealthCheckOptions());

internal override SecretClient CreateComponentClient(Uri vaultUri, SecretClientOptions options, TokenCredential cred)
=> new(vaultUri, cred, options);
}
Loading
Oops, something went wrong.