Skip to content
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

Farmer support for network interface #1039

Merged
merged 5 commits into from
Jun 5, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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
3 changes: 3 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
Release Notes
=============
## 1.7.23
* Network Interface: Adds support for network interface creation.
*

## 1.7.22
* AVS: Scripting subresource types.
Expand Down
52 changes: 52 additions & 0 deletions docs/content/api-overview/resources/network-interface.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
title: "Network Interface"
chapter: false
weight: 5
---

#### Overview
The `networkInterface` builder allows you to create network interfaces (NIC) so that Azure virtual machine (VM) can
communicate with internet, Azure, and on-premises resources. To learn more about routeServer, reference to
[Azure Docs](https://learn.microsoft.com/en-us/azure/virtual-network/virtual-network-network-interface?tabs=azure-portal)

* NetworkInterface (`Microsoft.Network/networkInterfaces`)

#### Builder Keywords

| Applies To | Keyword | Purpose |
|-|------------------|--------------------------------------------------------------------------------------------|
| networkInterface | name | Name of the network interface resource |
| networkInterface | subnet_prefix | Sets the subnet prefix of the vnet for network interface |
| networkInterface | link_to_vnet | Link to existing vnet or to vnet managed by Farmer |
| networkInterface | add_static_ip | Use static ip for the network interface. If not provided, ip will be dynamically allocated |
| networkInterface | accelerated_networking_flag | The accelerated networking flag for the network interface. Default is false |
| networkInterface | ip_forwarding_flag | The ip forwarding flag for the network interface. Default is false |

#### Example

```fsharp
#r "nuget:Farmer"
open Farmer
open Farmer.Builders
open Farmer.Builders.NetworkInterface

arm {
location Location.EastUS

add_resources
[
vnet {
name "test-vnet"
add_address_spaces [ "10.0.0.0/16" ]
}
networkInterface {
name "my-network-interface"
subnet_prefix "10.0.100.0/24"
link_to_vnet (virtualNetworks.resourceId "test-vnet")
add_static_ip "10.0.100.10"
accelerated_networking_flag false
ip_forwarding_flag false
}
]
}
```
135 changes: 135 additions & 0 deletions src/Farmer/Builders/Builders.NetworkInterface.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
module Farmer.Builders.NetworkInterface

open Farmer
open Farmer.Arm
open Farmer
open Farmer.Builders
open Farmer.Network
open Farmer.Arm.Network

type NetworkInterfaceConfig =
{
Name: ResourceName
AcceleratedNetworkingflag: FeatureFlag option
IpForwarding: FeatureFlag option
IsPrimary: bool option
VirtualNetwork: LinkedResource option
SubnetPrefix: IPAddressCidr
PrivateIpAddress: string
Huaxinli123 marked this conversation as resolved.
Show resolved Hide resolved
Tags: Map<string, string>
}

interface IBuilder with
member this.ResourceId = networkInterfaces.resourceId this.Name

member this.BuildResources location =
[
//vnet
let vnetId =
this.VirtualNetwork
|> Option.defaultWith (fun _ -> raiseFarmer "Must set 'vnet' for network interface")

//subnet
{
Huaxinli123 marked this conversation as resolved.
Show resolved Hide resolved
Subnet.Name = ResourceName "networkInterfaceSubnet"
Prefix = IPAddressCidr.format this.SubnetPrefix
VirtualNetwork = Some(vnetId)
NetworkSecurityGroup = None
Delegations = []
NatGateway = None
ServiceEndpoints = []
AssociatedServiceEndpointPolicies = []
PrivateEndpointNetworkPolicies = None
PrivateLinkServiceNetworkPolicies = None
}

//ipConfig
let subnetIpConfigs =
[
{
SubnetName = ResourceName "networkInterfaceSubnet"
Huaxinli123 marked this conversation as resolved.
Show resolved Hide resolved
LoadBalancerBackendAddressPools = []
PublicIpAddress = None
PrivateIpAllocation =
match this.PrivateIpAddress with
| "" -> Some(AllocationMethod.DynamicPrivateIp)
| ip -> Some(AllocationMethod.StaticPrivateIp(System.Net.IPAddress.Parse ip))
Primary = this.IsPrimary
}
]

//network interface
{
Name = this.Name
Location = location
EnableAcceleratedNetworking = this.AcceleratedNetworkingflag |> Option.map (fun f -> f.AsBoolean)
EnableIpForwarding = this.IpForwarding |> Option.map (fun f -> f.AsBoolean)
IpConfigs = subnetIpConfigs
Primary = this.IsPrimary
VirtualNetwork = vnetId
NetworkSecurityGroup = None
Tags = this.Tags
}
]

type NetworkInterfaceBuilder() =
member _.Yield _ =
{
Name = ResourceName.Empty
AcceleratedNetworkingflag = None
IpForwarding = None
IsPrimary = None
VirtualNetwork = None
SubnetPrefix =
{
Address = System.Net.IPAddress.Parse("10.0.100.0")
Prefix = 16
}
PrivateIpAddress = ""
Tags = Map.empty
}

[<CustomOperation "name">]
member _.Name(state: NetworkInterfaceConfig, name: string) = { state with Name = ResourceName name }

[<CustomOperation "accelerated_networking_flag">]
member _.AcceleratedNetworkingflag(state: NetworkInterfaceConfig, flag: bool) =
{ state with
AcceleratedNetworkingflag = Some(FeatureFlag.ofBool flag)
}

[<CustomOperation "ip_forwarding_flag">]
member _.IpForwarding(state: NetworkInterfaceConfig, flag: bool) =
{ state with
IpForwarding = Some(FeatureFlag.ofBool flag)
}

[<CustomOperation "is_primary">]
member _.IsPrimary(state: NetworkInterfaceConfig, isPrimary: bool) =
{ state with
IsPrimary = Some(isPrimary)
}

// linked to managed vnet created by Farmer and linked by user
[<CustomOperation "link_to_vnet">]
member _.LinkToVNetId(state: NetworkInterfaceConfig, vnetId: ResourceId) =
{ state with
VirtualNetwork = Some(Managed vnetId)
}

// linked to external existing vnet
member _.LinkToVNetId(state: NetworkInterfaceConfig, vnetName: string) =
{ state with
VirtualNetwork = Some(Unmanaged(virtualNetworks.resourceId (ResourceName vnetName)))
}

[<CustomOperation "subnet_prefix">]
member _.SubnetPrefix(state: NetworkInterfaceConfig, prefix) =
{ state with
SubnetPrefix = IPAddressCidr.parse prefix
}

[<CustomOperation "add_static_ip">]
member _.StaticIpAllocation(state: NetworkInterfaceConfig, addr) = { state with PrivateIpAddress = addr }

let networkInterface = NetworkInterfaceBuilder()
1 change: 1 addition & 0 deletions src/Farmer/Farmer.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@
<Compile Include="Builders\Builders.PrivateEndpoint.fs" />
<Compile Include="Builders\Builders.RouteTable.fs" />
<Compile Include="Builders\Builders.HostGroup.fs" />
<Compile Include="Builders\Builders.NetworkInterface.fs" />
<Compile Include="Aliases.fs" />
</ItemGroup>
</Project>
145 changes: 145 additions & 0 deletions src/Tests/Network.fs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ open Newtonsoft.Json.Linq
open Farmer
open Farmer.Arm
open Farmer.Builders
open Farmer.Builders.NetworkInterface
open Farmer.Network
open Microsoft.Rest

Expand Down Expand Up @@ -828,4 +829,148 @@ let tests =
"[resourceId('farmer-pls', 'Microsoft.Network/privateLinkServices', 'pls')]"
"Incorrect private link service ID"
}

test "Creates basic network interface with static ip" {
let deployment =
arm {
location Location.EastUS

add_resources
[
vnet {
name "test-vnet"
add_address_spaces [ "10.0.0.0/16" ]
}
networkInterface {
name "my-network-interface"
subnet_prefix "10.0.100.0/24"
link_to_vnet (virtualNetworks.resourceId "test-vnet")
add_static_ip "10.0.100.10"
accelerated_networking_flag false
ip_forwarding_flag false
}
]
}

let jobj = deployment.Template |> Writer.toJson |> JObject.Parse
let templateStr = jobj.ToString()

//validate vnet generated
let vnet =
jobj.SelectToken "resources[?(@.type=='Microsoft.Network/virtualNetworks')]"

Expect.isNotNull vnet "vnet should be generated"

//validate subnet generated
let subnet =
jobj.SelectToken "resources[?(@.type=='Microsoft.Network/virtualNetworks/subnets')]"

Expect.isNotNull subnet "subnet should be generated"

let subnetName = subnet.["name"]
Expect.equal subnetName "test-vnet/networkInterfaceSubnet" "Incorrect default value for subnet name"

let subnetProps = subnet.["properties"]
let addressPrefix: string = JToken.op_Explicit subnetProps.["addressPrefix"]
Expect.equal addressPrefix "10.0.100.0/24" "Incorrect addressPrefix for subnet"

//validate network interface generated
let networkInterface =
jobj.SelectToken "resources[?(@.type=='Microsoft.Network/networkInterfaces')]"

Expect.isNotNull networkInterface "network interface should be generated"

let networkInterfaceName = networkInterface.["name"]

Expect.equal
networkInterfaceName
"my-network-interface"
"Incorrect default value for network interface name"

let networkInterfaceDependencies =
jobj.SelectToken "resources[?(@.type=='Microsoft.Network/networkInterfaces')].dependsOn"
:?> Newtonsoft.Json.Linq.JArray

Expect.isNotNull networkInterfaceDependencies "Missing dependency for networkInterface"
Expect.hasLength networkInterfaceDependencies 1 "Incorrect number of dependencies for networkInterface"

Expect.equal
(networkInterfaceDependencies.[0].ToString())
"[resourceId(\u0027Microsoft.Network/virtualNetworks\u0027, \u0027test-vnet\u0027)]"
"Incorrect networkInterface dependencies"

let networkInterfaceProps = networkInterface.["properties"]

let enableAcceleratedNetworking: bool =
JToken.op_Explicit networkInterfaceProps.["enableAcceleratedNetworking"]

Expect.equal enableAcceleratedNetworking false "Incorrect default value for enableAcceleratedNetworking"

let enableIPForwarding: bool =
JToken.op_Explicit networkInterfaceProps.["enableIPForwarding"]

Expect.equal enableIPForwarding false "Incorrect default value for enableIPForwarding"

//validate ip config generated
let ipConfig = networkInterfaceProps.["ipConfigurations"].[0]
Expect.isNotNull ipConfig "network interface ip config should be generated"

let ipConfigName = ipConfig.["name"]
Expect.equal ipConfigName "ipconfig1" "Incorrect default value for network interface ip config name"

let ipConfigProps = ipConfig.["properties"]

let privateIPAddress: string = JToken.op_Explicit ipConfigProps.["privateIPAddress"]
Expect.equal privateIPAddress "10.0.100.10" "Incorrect default value for privateIPAddress"

let privateIPAllocationMethod: string =
JToken.op_Explicit ipConfigProps.["privateIPAllocationMethod"]

Expect.equal privateIPAllocationMethod "Static" "Incorrect default value for privateIPAllocationMethod"

let subnetId = ipConfigProps.SelectToken("subnet.id").ToString()

Expect.equal
subnetId
"[resourceId(\u0027Microsoft.Network/virtualNetworks/subnets\u0027, \u0027test-vnet\u0027, \u0027networkInterfaceSubnet\u0027)]"
"Incorrect subnet id for ipConfig"
}

test "Creates basic network interface with dynamic ip" {
let deployment =
arm {
location Location.EastUS

add_resources
[
vnet {
name "test-vnet"
add_address_spaces [ "10.0.0.0/16" ]
}
networkInterface {
name "my-network-interface"
subnet_prefix "10.0.100.0/24"
link_to_vnet (virtualNetworks.resourceId "test-vnet")
}
]
}

let jobj = deployment.Template |> Writer.toJson |> JObject.Parse
let templateStr = jobj.ToString()

let networkInterface =
jobj.SelectToken "resources[?(@.type=='Microsoft.Network/networkInterfaces')]"

Expect.isNotNull networkInterface "network interface should be generated"

let ipConfig = networkInterface.["properties"].["ipConfigurations"].[0]
Expect.isNotNull ipConfig "network interface ip config should be generated"

let ipConfigProps = ipConfig.["properties"]

let privateIPAllocationMethod: string =
JToken.op_Explicit ipConfigProps.["privateIPAllocationMethod"]

Expect.equal privateIPAllocationMethod "Dynamic" "Incorrect default value for privateIPAllocationMethod"
}
]