Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
71 changes: 59 additions & 12 deletions src/Aspire.Hosting.Azure.Network/AzureSubnetResource.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// 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;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using Aspire.Hosting.ApplicationModel;
Expand All @@ -13,35 +14,68 @@ namespace Aspire.Hosting.Azure;
/// <summary>
/// Represents an Azure Subnet resource.
/// </summary>
/// <param name="name">The name of the resource.</param>
/// <param name="subnetName">The subnet name.</param>
/// <param name="addressPrefix">The address prefix for the subnet.</param>
/// <param name="parent">The parent Virtual Network resource.</param>
/// <remarks>
/// Use <see cref="AzureProvisioningResourceExtensions.ConfigureInfrastructure{T}(ApplicationModel.IResourceBuilder{T}, Action{AzureResourceInfrastructure})"/> to configure specific <see cref="Azure.Provisioning"/> properties.
/// </remarks>
public class AzureSubnetResource(string name, string subnetName, string addressPrefix, AzureVirtualNetworkResource parent)
: Resource(name), IResourceWithParent<AzureVirtualNetworkResource>
public class AzureSubnetResource : Resource, IResourceWithParent<AzureVirtualNetworkResource>
{
// Backing field holds either string or ParameterResource
private readonly object _addressPrefix;

/// <summary>
/// Initializes a new instance of the <see cref="AzureSubnetResource"/> class.
/// </summary>
/// <param name="name">The name of the resource.</param>
/// <param name="subnetName">The subnet name.</param>
/// <param name="addressPrefix">The address prefix for the subnet.</param>
/// <param name="parent">The parent Virtual Network resource.</param>
public AzureSubnetResource(string name, string subnetName, string addressPrefix, AzureVirtualNetworkResource parent)
: base(name)
{
SubnetName = ThrowIfNullOrEmpty(subnetName);
_addressPrefix = ThrowIfNullOrEmpty(addressPrefix);
Parent = parent ?? throw new ArgumentNullException(nameof(parent));
}

/// <summary>
/// Initializes a new instance of the <see cref="AzureSubnetResource"/> class with a parameterized address prefix.
/// </summary>
/// <param name="name">The name of the resource.</param>
/// <param name="subnetName">The subnet name.</param>
/// <param name="addressPrefix">The parameter resource containing the address prefix for the subnet.</param>
/// <param name="parent">The parent Virtual Network resource.</param>
public AzureSubnetResource(string name, string subnetName, ParameterResource addressPrefix, AzureVirtualNetworkResource parent)
: base(name)
{
SubnetName = ThrowIfNullOrEmpty(subnetName);
_addressPrefix = addressPrefix ?? throw new ArgumentNullException(nameof(addressPrefix));
Parent = parent ?? throw new ArgumentNullException(nameof(parent));
}

/// <summary>
/// Gets the subnet name.
/// </summary>
public string SubnetName { get; } = ThrowIfNullOrEmpty(subnetName);
public string SubnetName { get; }

/// <summary>
/// Gets the address prefix for the subnet (e.g., "10.0.1.0/24").
/// Gets the address prefix for the subnet (e.g., "10.0.1.0/24"), or <c>null</c> if the address prefix is provided via a <see cref="ParameterResource"/>.
/// </summary>
public string AddressPrefix { get; } = ThrowIfNullOrEmpty(addressPrefix);
public string? AddressPrefix => _addressPrefix as string;

/// <summary>
/// Gets the parameter resource containing the address prefix for the subnet, or <c>null</c> if the address prefix is provided as a literal string.
/// </summary>
public ParameterResource? AddressPrefixParameter => _addressPrefix as ParameterResource;

/// <summary>
/// Gets the subnet Id output reference.
/// </summary>
public BicepOutputReference Id => new($"{Infrastructure.NormalizeBicepIdentifier(Name)}_Id", parent);
public BicepOutputReference Id => new($"{Infrastructure.NormalizeBicepIdentifier(Name)}_Id", Parent);

/// <summary>
/// Gets the parent Azure Virtual Network resource.
/// </summary>
public AzureVirtualNetworkResource Parent { get; } = parent ?? throw new ArgumentNullException(nameof(parent));
public AzureVirtualNetworkResource Parent { get; }

private static string ThrowIfNullOrEmpty([NotNull] string? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null)
=> !string.IsNullOrEmpty(argument) ? argument : throw new ArgumentNullException(paramName);
Expand All @@ -54,9 +88,22 @@ internal SubnetResource ToProvisioningEntity(AzureResourceInfrastructure infra,
var subnet = new SubnetResource(Infrastructure.NormalizeBicepIdentifier(Name))
{
Name = SubnetName,
AddressPrefix = AddressPrefix,
};

// Set the address prefix from either the literal string or the parameter
if (_addressPrefix is string addressPrefix)
{
subnet.AddressPrefix = addressPrefix;
}
else if (_addressPrefix is ParameterResource addressPrefixParameter)
{
subnet.AddressPrefix = addressPrefixParameter.AsProvisioningParameter(infra);
}
else
{
throw new UnreachableException("AddressPrefix must be set either as a string or a ParameterResource.");
}

if (dependsOn is not null)
{
subnet.DependsOn.Add(dependsOn);
Expand Down
170 changes: 130 additions & 40 deletions src/Aspire.Hosting.Azure.Network/AzureVirtualNetworkExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,66 +38,116 @@ public static IResourceBuilder<AzureVirtualNetworkResource> AddAzureVirtualNetwo

builder.AddAzureProvisioning();

AzureVirtualNetworkResource resource = new(name, ConfigureVirtualNetwork);
AzureVirtualNetworkResource resource = new(name, ConfigureVirtualNetwork, addressPrefix);

return AddAzureVirtualNetworkCore(builder, resource);
}

/// <summary>
/// Adds an Azure Virtual Network resource to the application model with a parameterized address prefix.
/// </summary>
/// <param name="builder">The builder for the distributed application.</param>
/// <param name="name">The name of the Azure Virtual Network resource.</param>
/// <param name="addressPrefix">The parameter resource containing the address prefix for the virtual network (e.g., "10.0.0.0/16").</param>
/// <returns>A reference to the <see cref="IResourceBuilder{AzureVirtualNetworkResource}"/>.</returns>
/// <example>
/// This example creates a virtual network with a parameterized address prefix:
/// <code>
/// var vnetPrefix = builder.AddParameter("vnetPrefix");
/// var vnet = builder.AddAzureVirtualNetwork("vnet", vnetPrefix);
/// var subnet = vnet.AddSubnet("pe-subnet", "10.0.1.0/24");
/// </code>
/// </example>
public static IResourceBuilder<AzureVirtualNetworkResource> AddAzureVirtualNetwork(
this IDistributedApplicationBuilder builder,
[ResourceName] string name,
IResourceBuilder<ParameterResource> addressPrefix)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentException.ThrowIfNullOrEmpty(name);
ArgumentNullException.ThrowIfNull(addressPrefix);

builder.AddAzureProvisioning();

AzureVirtualNetworkResource resource = new(name, ConfigureVirtualNetwork, addressPrefix.Resource);

return AddAzureVirtualNetworkCore(builder, resource);
}

private static IResourceBuilder<AzureVirtualNetworkResource> AddAzureVirtualNetworkCore(
IDistributedApplicationBuilder builder,
AzureVirtualNetworkResource resource)
{
if (builder.ExecutionContext.IsRunMode)
{
// In run mode, we don't want to add the resource to the builder.
return builder.CreateResourceBuilder(resource);
}

return builder.AddResource(resource);
}

void ConfigureVirtualNetwork(AzureResourceInfrastructure infra)
{
var vnet = AzureProvisioningResource.CreateExistingOrNewProvisionableResource(infra,
(identifier, name) =>
private static void ConfigureVirtualNetwork(AzureResourceInfrastructure infra)
{
var azureResource = (AzureVirtualNetworkResource)infra.AspireResource;

var vnet = AzureProvisioningResource.CreateExistingOrNewProvisionableResource(infra,
(identifier, name) =>
{
var resource = VirtualNetwork.FromExisting(identifier);
resource.Name = name;
return resource;
},
(infrastructure) =>
{
var vnet = new VirtualNetwork(infrastructure.AspireResource.GetBicepIdentifier())
{
var resource = VirtualNetwork.FromExisting(identifier);
resource.Name = name;
return resource;
},
(infrastructure) =>
Tags = { { "aspire-resource-name", infrastructure.AspireResource.Name } }
};

// Set the address prefix from either the literal string or the parameter
if (azureResource.AddressPrefix is { } addressPrefix)
{
var vnet = new VirtualNetwork(infrastructure.AspireResource.GetBicepIdentifier())
vnet.AddressSpace = new VirtualNetworkAddressSpace()
{
AddressSpace = new VirtualNetworkAddressSpace()
{
AddressPrefixes = { addressPrefix ?? "10.0.0.0/16" }
},
Tags = { { "aspire-resource-name", infrastructure.AspireResource.Name } }
AddressPrefixes = { addressPrefix }
};
}
else if (azureResource.AddressPrefixParameter is { } addressPrefixParameter)
{
vnet.AddressSpace = new VirtualNetworkAddressSpace()
{
AddressPrefixes = { addressPrefixParameter.AsProvisioningParameter(infrastructure) }
};
}

return vnet;
});

var azureResource = (AzureVirtualNetworkResource)infra.AspireResource;
return vnet;
});

// Add subnets
if (azureResource.Subnets.Count > 0)
// Add subnets
if (azureResource.Subnets.Count > 0)
{
// Chain subnet provisioning to ensure deployment doesn't fail
// due to parallel creation of subnets within the VNet.
ProvisionableResource? dependsOn = null;
foreach (var subnet in azureResource.Subnets)
{
// Chain subnet provisioning to ensure deployment doesn't fail
// due to parallel creation of subnets within the VNet.
ProvisionableResource? dependsOn = null;
foreach (var subnet in azureResource.Subnets)
{
var cdkSubnet = subnet.ToProvisioningEntity(infra, dependsOn);
cdkSubnet.Parent = vnet;
infra.Add(cdkSubnet);
var cdkSubnet = subnet.ToProvisioningEntity(infra, dependsOn);
cdkSubnet.Parent = vnet;
infra.Add(cdkSubnet);

dependsOn = cdkSubnet;
}
dependsOn = cdkSubnet;
}
}

// Output the VNet ID for references
infra.Add(new ProvisioningOutput("id", typeof(string))
{
Value = vnet.Id
});
// Output the VNet ID for references
infra.Add(new ProvisioningOutput("id", typeof(string))
{
Value = vnet.Id
});

// We need to output name so it can be referenced by others.
infra.Add(new ProvisioningOutput("name", typeof(string)) { Value = vnet.Name });
}
// We need to output name so it can be referenced by others.
infra.Add(new ProvisioningOutput("name", typeof(string)) { Value = vnet.Name });
}

/// <summary>
Expand Down Expand Up @@ -129,6 +179,46 @@ public static IResourceBuilder<AzureSubnetResource> AddSubnet(

var subnet = new AzureSubnetResource(name, subnetName, addressPrefix, builder.Resource);

return AddSubnetCore(builder, subnet);
}

/// <summary>
/// Adds an Azure Subnet to the Virtual Network with a parameterized address prefix.
/// </summary>
/// <param name="builder">The Virtual Network resource builder.</param>
/// <param name="name">The name of the subnet resource.</param>
/// <param name="addressPrefix">The parameter resource containing the address prefix for the subnet (e.g., "10.0.1.0/24").</param>
/// <param name="subnetName">The subnet name in Azure. If null, the resource name is used.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{AzureSubnetResource}"/>.</returns>
/// <example>
/// This example adds a subnet with a parameterized address prefix:
/// <code>
/// var subnetPrefix = builder.AddParameter("subnetPrefix");
/// var vnet = builder.AddAzureVirtualNetwork("vnet");
/// var subnet = vnet.AddSubnet("my-subnet", subnetPrefix);
/// </code>
/// </example>
public static IResourceBuilder<AzureSubnetResource> AddSubnet(
this IResourceBuilder<AzureVirtualNetworkResource> builder,
[ResourceName] string name,
IResourceBuilder<ParameterResource> addressPrefix,
string? subnetName = null)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentException.ThrowIfNullOrEmpty(name);
ArgumentNullException.ThrowIfNull(addressPrefix);

subnetName ??= name;

var subnet = new AzureSubnetResource(name, subnetName, addressPrefix.Resource, builder.Resource);

return AddSubnetCore(builder, subnet);
}

private static IResourceBuilder<AzureSubnetResource> AddSubnetCore(
IResourceBuilder<AzureVirtualNetworkResource> builder,
AzureSubnetResource subnet)
{
builder.Resource.Subnets.Add(subnet);

if (builder.ApplicationBuilder.ExecutionContext.IsRunMode)
Expand Down
41 changes: 41 additions & 0 deletions src/Aspire.Hosting.Azure.Network/AzureVirtualNetworkResource.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// 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.Network;
using Azure.Provisioning.Primitives;

Expand All @@ -14,8 +15,23 @@ namespace Aspire.Hosting.Azure;
public class AzureVirtualNetworkResource(string name, Action<AzureResourceInfrastructure> configureInfrastructure)
: AzureProvisioningResource(name, configureInfrastructure)
{
private const string DefaultAddressPrefix = "10.0.0.0/16";

// Backing field holds either string or ParameterResource
private readonly object _addressPrefix = DefaultAddressPrefix;

internal List<AzureSubnetResource> Subnets { get; } = [];

/// <summary>
/// Gets the address prefix for the virtual network (e.g., "10.0.0.0/16"), or <c>null</c> if the address prefix is provided via a <see cref="ParameterResource"/>.
/// </summary>
public string? AddressPrefix => _addressPrefix as string;

/// <summary>
/// Gets the parameter resource containing the address prefix for the virtual network, or <c>null</c> if the address prefix is provided as a literal string.
/// </summary>
public ParameterResource? AddressPrefixParameter => _addressPrefix as ParameterResource;

/// <summary>
/// Gets the "id" output reference from the Azure Virtual Network resource.
/// </summary>
Expand All @@ -26,6 +42,31 @@ public class AzureVirtualNetworkResource(string name, Action<AzureResourceInfras
/// </summary>
public BicepOutputReference NameOutput => new("name", this);

/// <summary>
/// Initializes a new instance of the <see cref="AzureVirtualNetworkResource"/> class with a string address prefix.
/// </summary>
/// <param name="name">The name of the resource.</param>
/// <param name="configureInfrastructure">Callback to configure the Azure Virtual Network resource.</param>
/// <param name="addressPrefix">The address prefix for the virtual network (e.g., "10.0.0.0/16").</param>
public AzureVirtualNetworkResource(string name, Action<AzureResourceInfrastructure> configureInfrastructure, string? addressPrefix)
: this(name, configureInfrastructure)
{
_addressPrefix = addressPrefix ?? DefaultAddressPrefix;
}

/// <summary>
/// Initializes a new instance of the <see cref="AzureVirtualNetworkResource"/> class with a parameterized address prefix.
/// </summary>
/// <param name="name">The name of the resource.</param>
/// <param name="configureInfrastructure">Callback to configure the Azure Virtual Network resource.</param>
/// <param name="addressPrefix">The parameter resource containing the address prefix for the virtual network.</param>
public AzureVirtualNetworkResource(string name, Action<AzureResourceInfrastructure> configureInfrastructure, ParameterResource addressPrefix)
: this(name, configureInfrastructure)
{
ArgumentNullException.ThrowIfNull(addressPrefix);
_addressPrefix = addressPrefix;
}

/// <inheritdoc/>
public override ProvisionableResource AddAsExistingResource(AzureResourceInfrastructure infra)
{
Expand Down
Loading
Loading