|
// 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.Hosting.ApplicationModel;
using Aspire.Hosting.Azure;
using Azure.Provisioning;
using Azure.Provisioning.KeyVault;
namespace Aspire.Hosting;
/// <summary>
/// Extensions for working with <see cref="AzureProvisioningResource"/> and related types.
/// </summary>
public static class AzureProvisioningResourceExtensions
{
/// <summary>
/// Adds an Azure provisioning resource to the application model.
/// </summary>
/// <param name="builder">The distributed application builder.</param>
/// <param name="name">The name of the resource being added.</param>
/// <param name="configureInfrastructure">A callback used to configure the infrastructure resource.</param>
/// <returns></returns>
public static IResourceBuilder<AzureProvisioningResource> AddAzureInfrastructure(this IDistributedApplicationBuilder builder, [ResourceName] string name, Action<AzureResourceInfrastructure> configureInfrastructure)
{
builder.AddAzureProvisioning();
var resource = new AzureProvisioningResource(name, configureInfrastructure);
return builder.AddResource(resource);
}
/// <summary>
/// Configures the Azure provisioning resource <see cref="Infrastructure"/>.
/// </summary>
/// <typeparam name="T">Type of the <see cref="AzureProvisioningResource"/> resource.</typeparam>
/// <param name="builder">The resource builder.</param>
/// <param name="configure">The configuration callback.</param>
/// <returns>The resource builder.</returns>
public static IResourceBuilder<T> ConfigureInfrastructure<T>(this IResourceBuilder<T> builder, Action<AzureResourceInfrastructure> configure)
where T : AzureProvisioningResource
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(configure);
builder.Resource.ConfigureInfrastructure += configure;
return builder;
}
/// <summary>
/// Gets or creates a <see cref="KeyVaultSecret"/> resource in the specified <see cref="AzureResourceInfrastructure"/>
/// for the given <see cref="IAzureKeyVaultSecretReference"/>.
/// <para>
/// If the referenced Key Vault or secret does not already exist in the infrastructure, they will be created and added.
/// This allows referencing secrets that are provisioned outside of the current deployment.
/// </para>
/// </summary>
/// <param name="secretReference">The <see cref="IAzureKeyVaultSecretReference"/> representing the Key Vault secret to reference.</param>
/// <param name="infrastructure">The <see cref="AzureResourceInfrastructure"/> in which to locate or add the <see cref="KeyVaultSecret"/>.</param>
/// <returns>
/// The <see cref="KeyVaultSecret"/> instance corresponding to the given secret reference.
/// </returns>
public static KeyVaultSecret AsKeyVaultSecret(this IAzureKeyVaultSecretReference secretReference, AzureResourceInfrastructure infrastructure)
{
ArgumentNullException.ThrowIfNull(secretReference);
ArgumentNullException.ThrowIfNull(infrastructure);
var resources = infrastructure.GetProvisionableResources();
var parameter = secretReference.Resource.NameOutputReference.AsProvisioningParameter(infrastructure);
var kvName = Infrastructure.NormalizeBicepIdentifier($"{parameter.BicepIdentifier}_kv");
var kv = resources.OfType<KeyVaultService>().SingleOrDefault(kv => kv.BicepIdentifier == kvName);
if (kv is null)
{
kv = KeyVaultService.FromExisting(kvName);
kv.Name = parameter;
infrastructure.Add(kv);
}
var kvsName = Infrastructure.NormalizeBicepIdentifier($"{kv.BicepIdentifier}_{secretReference.SecretName}");
var kvs = resources.OfType<KeyVaultSecret>().SingleOrDefault(kvSecret => kvSecret.BicepIdentifier == kvsName);
if (kvs is null)
{
kvs = KeyVaultSecret.FromExisting(kvsName);
kvs.Name = secretReference.SecretName;
kvs.Parent = kv;
infrastructure.Add(kvs);
}
return kvs;
}
/// <summary>
/// Creates a new <see cref="ProvisioningParameter"/> in <paramref name="infrastructure"/>, or reuses an existing bicep parameter if one with
/// the same name already exists, that corresponds to <paramref name="parameterResourceBuilder"/>.
/// </summary>
/// <param name="parameterResourceBuilder">
/// The <see cref="IResourceBuilder{ParameterResource}"/> that represents a parameter in the <see cref="Aspire.Hosting.ApplicationModel" />
/// to get or create a corresponding <see cref="ProvisioningParameter"/>.
/// </param>
/// <param name="infrastructure">The <see cref="AzureResourceInfrastructure"/> that contains the <see cref="ProvisioningParameter"/>.</param>
/// <param name="parameterName">The name of the parameter to be assigned.</param>
/// <returns>
/// The corresponding <see cref="ProvisioningParameter"/> that was found or newly created.
/// </returns>
/// <remarks>
/// This is useful when assigning a <see cref="BicepValue"/> to the value of an Aspire <see cref="ParameterResource"/>.
/// </remarks>
[SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters",
Justification = "The 'this' arguments are mutually exclusive")]
public static ProvisioningParameter AsProvisioningParameter(this IResourceBuilder<ParameterResource> parameterResourceBuilder, AzureResourceInfrastructure infrastructure, string? parameterName = null)
{
ArgumentNullException.ThrowIfNull(parameterResourceBuilder);
ArgumentNullException.ThrowIfNull(infrastructure);
return parameterResourceBuilder.Resource.AsProvisioningParameter(infrastructure, parameterName);
}
/// <summary>
/// Creates a new <see cref="ProvisioningParameter"/> in <paramref name="infrastructure"/>, or reuses an existing bicep parameter if one with
/// </summary>
/// <param name="manifestExpressionProvider">The <see cref="IManifestExpressionProvider"/> that represents the value to use for the <see cref="ProvisioningParameter"/>. </param>
/// <param name="infrastructure">The <see cref="AzureResourceInfrastructure"/> that contains the <see cref="ProvisioningParameter"/>.</param>
/// <param name="parameterName">The name of the parameter to be assigned.</param>
/// <param name="isSecure">Indicates whether the parameter is secure.</param>
/// <returns></returns>
public static ProvisioningParameter AsProvisioningParameter(this IManifestExpressionProvider manifestExpressionProvider, AzureResourceInfrastructure infrastructure, string? parameterName = null, bool? isSecure = null)
{
ArgumentNullException.ThrowIfNull(manifestExpressionProvider);
ArgumentNullException.ThrowIfNull(infrastructure);
parameterName ??= GetNameFromValueExpression(manifestExpressionProvider);
infrastructure.AspireResource.Parameters[parameterName] = manifestExpressionProvider;
return GetOrAddParameter(infrastructure, parameterName, isSecure);
}
/// <summary>
/// Creates a new <see cref="ProvisioningParameter"/> in <paramref name="infrastructure"/>, or reuses an existing bicep parameter if one with
/// the same name already exists, that corresponds to <paramref name="parameterResource"/>.
/// </summary>
/// <param name="parameterResource">
/// The <see cref="ParameterResource"/> that represents a parameter in the <see cref="Aspire.Hosting.ApplicationModel" />
/// to get or create a corresponding <see cref="ProvisioningParameter"/>.
/// </param>
/// <param name="infrastructure">The <see cref="AzureResourceInfrastructure"/> that contains the <see cref="ProvisioningParameter"/>.</param>
/// <param name="parameterName">The name of the parameter to be assigned.</param>
/// <returns>
/// The corresponding <see cref="ProvisioningParameter"/> that was found or newly created.
/// </returns>
/// <remarks>
/// This is useful when assigning a <see cref="BicepValue"/> to the value of an Aspire <see cref="ParameterResource"/>.
/// </remarks>
[SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters",
Justification = "The 'this' arguments are mutually exclusive")]
public static ProvisioningParameter AsProvisioningParameter(this ParameterResource parameterResource, AzureResourceInfrastructure infrastructure, string? parameterName = null)
{
ArgumentNullException.ThrowIfNull(parameterResource);
ArgumentNullException.ThrowIfNull(infrastructure);
parameterName ??= Infrastructure.NormalizeBicepIdentifier(parameterResource.Name);
infrastructure.AspireResource.Parameters[parameterName] = parameterResource;
return GetOrAddParameter(infrastructure, parameterName, parameterResource.Secret);
}
/// <summary>
/// Creates a new <see cref="ProvisioningParameter"/> in <paramref name="infrastructure"/>, or reuses an existing bicep parameter if one with
/// the same name already exists, that corresponds to <paramref name="outputReference"/>.
/// </summary>
/// <param name="outputReference">
/// The <see cref="BicepOutputReference"/> that contains the value to use for the <see cref="ProvisioningParameter"/>.
/// </param>
/// <param name="infrastructure">The <see cref="AzureResourceInfrastructure"/> that contains the <see cref="ProvisioningParameter"/>.</param>
/// <param name="parameterName">The name of the parameter to be assigned.</param>
/// <returns>
/// The corresponding <see cref="ProvisioningParameter"/> that was found or newly created.
/// </returns>
/// <remarks>
/// This is useful when assigning a <see cref="BicepValue"/> to the value of an Aspire <see cref="BicepOutputReference"/>.
/// </remarks>
[SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters",
Justification = "The 'this' arguments are mutually exclusive")]
public static ProvisioningParameter AsProvisioningParameter(this BicepOutputReference outputReference, AzureResourceInfrastructure infrastructure, string? parameterName = null)
{
ArgumentNullException.ThrowIfNull(outputReference);
ArgumentNullException.ThrowIfNull(infrastructure);
parameterName ??= GetNameFromValueExpression(outputReference);
infrastructure.AspireResource.Parameters[parameterName] = outputReference;
return GetOrAddParameter(infrastructure, parameterName);
}
/// <summary>
/// Creates a new <see cref="ProvisioningParameter"/> in <paramref name="infrastructure"/>, or reuses an existing bicep parameter if one with
/// the same name already exists, that corresponds to <paramref name="endpointReference"/>.
/// </summary>
/// <param name="endpointReference">
/// The <see cref="EndpointReference"/> to use for the value of the <see cref="ProvisioningParameter"/>.
/// </param>
/// <param name="infrastructure">The <see cref="AzureResourceInfrastructure"/> that contains the <see cref="ProvisioningParameter"/>.</param>
/// <param name="parameterName">The name of the parameter to be assigned.</param>
/// <returns>
/// The corresponding <see cref="ProvisioningParameter"/> that was found or newly created.
/// </returns>
/// <remarks>
/// This is useful when assigning a <see cref="BicepValue"/> to the value of an Aspire <see cref="EndpointReference"/>.
/// </remarks>
public static ProvisioningParameter AsProvisioningParameter(this EndpointReference endpointReference, AzureResourceInfrastructure infrastructure, string parameterName)
{
ArgumentNullException.ThrowIfNull(endpointReference);
ArgumentNullException.ThrowIfNull(infrastructure);
ArgumentException.ThrowIfNullOrEmpty(parameterName);
infrastructure.AspireResource.Parameters[parameterName] = endpointReference;
return GetOrAddParameter(infrastructure, parameterName);
}
/// <summary>
/// Creates a new <see cref="ProvisioningParameter"/> in <paramref name="infrastructure"/>, or reuses an existing bicep parameter if one with
/// the same name already exists, that corresponds to <paramref name="expression"/>.
/// </summary>
/// <param name="expression">
/// The <see cref="ReferenceExpression"/> that represents the value to use for the <see cref="ProvisioningParameter"/>.
/// </param>
/// <param name="infrastructure">The <see cref="AzureResourceInfrastructure"/> that contains the <see cref="ProvisioningParameter"/>.</param>
/// <param name="parameterName">The name of the parameter to be assigned.</param>
/// <returns>
/// The corresponding <see cref="ProvisioningParameter"/> that was found or newly created.
/// </returns>
/// <remarks>
/// This is useful when assigning a <see cref="BicepValue"/> to the value of an Aspire <see cref="EndpointReference"/>.
/// </remarks>
public static ProvisioningParameter AsProvisioningParameter(this ReferenceExpression expression, AzureResourceInfrastructure infrastructure, string parameterName)
{
ArgumentNullException.ThrowIfNull(expression);
ArgumentNullException.ThrowIfNull(infrastructure);
ArgumentException.ThrowIfNullOrEmpty(parameterName);
infrastructure.AspireResource.Parameters[parameterName] = expression;
return GetOrAddParameter(infrastructure, parameterName);
}
private static ProvisioningParameter GetOrAddParameter(AzureResourceInfrastructure infrastructure, string parameterName, bool? isSecure = null)
{
var parameter = infrastructure.GetParameters().FirstOrDefault(p => p.BicepIdentifier == parameterName);
if (parameter is null)
{
parameter = new ProvisioningParameter(parameterName, typeof(string));
if (isSecure.HasValue)
{
parameter.IsSecure = isSecure.Value;
}
infrastructure.Add(parameter);
}
return parameter;
}
private static string GetNameFromValueExpression(IManifestExpressionProvider ep)
{
var parameterName = ep.ValueExpression.Replace("{", "").Replace("}", "").Replace(".", "_").Replace("-", "_").ToLowerInvariant();
if (parameterName[0] == '_')
{
parameterName = parameterName[1..];
}
return parameterName;
}
}
|