From b7ebd5b392a0a96f732418cde482f4bd23cd6eb6 Mon Sep 17 00:00:00 2001 From: xuzhang3 <57888764+xuzhang3@users.noreply.github.com> Date: Tue, 24 Nov 2020 02:05:55 +0800 Subject: [PATCH] Add support ipv6 configuration in azurerm_express_route_circuit_peering (#9235) Co-authored-by: xuzhang3 Co-authored-by: jackofallops --- .../express_route_circuit_peering_resource.go | 135 +++++++++- ...ess_route_circuit_peering_resource_test.go | 240 ++++++++++++++++++ .../express_route_circuit_resource_test.go | 9 +- ...xpress_route_circuit_peering.html.markdown | 57 ++++- 4 files changed, 429 insertions(+), 12 deletions(-) diff --git a/azurerm/internal/services/network/express_route_circuit_peering_resource.go b/azurerm/internal/services/network/express_route_circuit_peering_resource.go index f35e349a6f15..b4cfb5b27f86 100644 --- a/azurerm/internal/services/network/express_route_circuit_peering_resource.go +++ b/azurerm/internal/services/network/express_route_circuit_peering_resource.go @@ -94,11 +94,13 @@ func resourceArmExpressRouteCircuitPeering() *schema.Resource { Required: true, Elem: &schema.Schema{Type: schema.TypeString}, }, + "customer_asn": { Type: schema.TypeInt, Optional: true, Default: 0, }, + "routing_registry_name": { Type: schema.TypeString, Optional: true, @@ -108,6 +110,63 @@ func resourceArmExpressRouteCircuitPeering() *schema.Resource { }, }, + "ipv6": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "microsoft_peering": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "advertised_public_prefixes": { + Type: schema.TypeList, + MinItems: 1, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.IsCIDR, + }, + }, + + "customer_asn": { + Type: schema.TypeInt, + Optional: true, + Default: 0, + }, + + "routing_registry_name": { + Type: schema.TypeString, + Optional: true, + Default: "NONE", + ValidateFunc: validation.StringIsNotEmpty, + }, + }, + }, + }, + + "primary_peer_address_prefix": { + Type: schema.TypeString, + Required: true, + }, + + "secondary_peer_address_prefix": { + Type: schema.TypeString, + Required: true, + }, + + "route_filter_id": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: azure.ValidateResourceID, + }, + }, + }, + }, + "azure_asn": { Type: schema.TypeInt, Computed: true, @@ -193,8 +252,22 @@ func resourceArmExpressRouteCircuitPeeringCreateUpdate(d *schema.ResourceData, m ID: utils.String(route_filter_id), } } - } else if route_filter_id != "" { - return fmt.Errorf("`route_filter_id` may only be specified when `peering_type` is set to `MicrosoftPeering`") + + ipv6Peering := d.Get("ipv6").([]interface{}) + ipv6PeeringConfig, err := expandExpressRouteCircuitIpv6PeeringConfig(ipv6Peering) + if err != nil { + return err + } + parameters.ExpressRouteCircuitPeeringPropertiesFormat.Ipv6PeeringConfig = ipv6PeeringConfig + } else { + if route_filter_id != "" { + return fmt.Errorf("`route_filter_id` may only be specified when `peering_type` is set to `MicrosoftPeering`") + } + + ipv6Peering := d.Get("ipv6").([]interface{}) + if len(ipv6Peering) != 0 { + return fmt.Errorf("`ipv6` may only be specified when `peering_type` is set to `MicrosoftPeering`") + } } future, err := client.CreateOrUpdate(ctx, resourceGroup, circuitName, peeringType, parameters) @@ -259,7 +332,10 @@ func resourceArmExpressRouteCircuitPeeringRead(d *schema.ResourceData, meta inte config := flattenExpressRouteCircuitPeeringMicrosoftConfig(props.MicrosoftPeeringConfig) if err := d.Set("microsoft_peering_config", config); err != nil { - return fmt.Errorf("Error setting `microsoft_peering_config`: %+v", err) + return fmt.Errorf("setting `microsoft_peering_config`: %+v", err) + } + if err := d.Set("ipv6", flattenExpressRouteCircuitIpv6PeeringConfig(props.Ipv6PeeringConfig)); err != nil { + return fmt.Errorf("setting `ipv6`: %+v", err) } } @@ -302,6 +378,9 @@ func resourceArmExpressRouteCircuitPeeringDelete(d *schema.ResourceData, meta in } func expandExpressRouteCircuitPeeringMicrosoftConfig(input []interface{}) *network.ExpressRouteCircuitPeeringConfig { + if len(input) == 0 { + return nil + } peering := input[0].(map[string]interface{}) prefixes := make([]string, 0) @@ -320,6 +399,29 @@ func expandExpressRouteCircuitPeeringMicrosoftConfig(input []interface{}) *netwo } } +func expandExpressRouteCircuitIpv6PeeringConfig(input []interface{}) (*network.Ipv6ExpressRouteCircuitPeeringConfig, error) { + if len(input) == 0 { + return nil, nil + } + + v := input[0].(map[string]interface{}) + peeringConfig := network.Ipv6ExpressRouteCircuitPeeringConfig{ + PrimaryPeerAddressPrefix: utils.String(v["primary_peer_address_prefix"].(string)), + SecondaryPeerAddressPrefix: utils.String(v["secondary_peer_address_prefix"].(string)), + MicrosoftPeeringConfig: expandExpressRouteCircuitPeeringMicrosoftConfig(v["microsoft_peering"].([]interface{})), + } + routeFilterId := v["route_filter_id"].(string) + if routeFilterId != "" { + if _, err := ParseRouteFilterID(routeFilterId); err != nil { + return nil, err + } + peeringConfig.RouteFilter = &network.SubResource{ + ID: utils.String(routeFilterId), + } + } + return &peeringConfig, nil +} + func flattenExpressRouteCircuitPeeringMicrosoftConfig(input *network.ExpressRouteCircuitPeeringConfig) interface{} { if input == nil { return []interface{}{} @@ -340,3 +442,30 @@ func flattenExpressRouteCircuitPeeringMicrosoftConfig(input *network.ExpressRout return []interface{}{config} } + +func flattenExpressRouteCircuitIpv6PeeringConfig(input *network.Ipv6ExpressRouteCircuitPeeringConfig) []interface{} { + if input == nil { + return make([]interface{}, 0) + } + + var primaryPeerAddressPrefix string + if input.PrimaryPeerAddressPrefix != nil { + primaryPeerAddressPrefix = *input.PrimaryPeerAddressPrefix + } + var secondaryPeerAddressPrefix string + if input.SecondaryPeerAddressPrefix != nil { + secondaryPeerAddressPrefix = *input.SecondaryPeerAddressPrefix + } + routeFilterId := "" + if input.RouteFilter != nil && input.RouteFilter.ID != nil { + routeFilterId = *input.RouteFilter.ID + } + return []interface{}{ + map[string]interface{}{ + "microsoft_peering": flattenExpressRouteCircuitPeeringMicrosoftConfig(input.MicrosoftPeeringConfig), + "primary_peer_address_prefix": primaryPeerAddressPrefix, + "secondary_peer_address_prefix": secondaryPeerAddressPrefix, + "route_filter_id": routeFilterId, + }, + } +} diff --git a/azurerm/internal/services/network/tests/express_route_circuit_peering_resource_test.go b/azurerm/internal/services/network/tests/express_route_circuit_peering_resource_test.go index 34e9a053977a..27fdf878d444 100644 --- a/azurerm/internal/services/network/tests/express_route_circuit_peering_resource_test.go +++ b/azurerm/internal/services/network/tests/express_route_circuit_peering_resource_test.go @@ -73,6 +73,63 @@ func testAccAzureRMExpressRouteCircuitPeering_microsoftPeering(t *testing.T) { }) } +func testAccAzureRMExpressRouteCircuitPeering_microsoftPeeringIpv6(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_express_route_circuit_peering", "test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMExpressRouteCircuitPeeringDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMExpressRouteCircuitPeering_msPeeringIpv6(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMExpressRouteCircuitPeeringExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} + +func testAccAzureRMExpressRouteCircuitPeering_microsoftPeeringIpv6CustomerRouting(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_express_route_circuit_peering", "test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMExpressRouteCircuitPeeringDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMExpressRouteCircuitPeering_msPeeringIpv6CustomerRouting(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMExpressRouteCircuitPeeringExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} + +func testAccAzureRMExpressRouteCircuitPeering_microsoftPeeringIpv6WithRouteFilter(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_express_route_circuit_peering", "test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMExpressRouteCircuitPeeringDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMExpressRouteCircuitPeering_msPeeringIpv6WithRouteFilter(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMExpressRouteCircuitPeeringExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} + func testAccAzureRMExpressRouteCircuitPeering_microsoftPeeringCustomerRouting(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_express_route_circuit_peering", "test") @@ -310,6 +367,189 @@ resource "azurerm_express_route_circuit_peering" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomInteger) } +func testAccAzureRMExpressRouteCircuitPeering_msPeeringIpv6(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-expressroute-%d" + location = "%s" +} + +resource "azurerm_express_route_circuit" "test" { + name = "acctest-erc-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + service_provider_name = "Equinix" + peering_location = "Silicon Valley" + bandwidth_in_mbps = 50 + + sku { + tier = "Premium" + family = "MeteredData" + } + + tags = { + Env = "Test" + Purpose = "AcceptanceTests" + } +} + +resource "azurerm_express_route_circuit_peering" "test" { + peering_type = "MicrosoftPeering" + express_route_circuit_name = azurerm_express_route_circuit.test.name + resource_group_name = azurerm_resource_group.test.name + peer_asn = 100 + primary_peer_address_prefix = "192.168.3.0/30" + secondary_peer_address_prefix = "192.168.4.0/30" + vlan_id = 300 + + microsoft_peering_config { + advertised_public_prefixes = ["123.2.0.0/24"] + } + + ipv6 { + primary_peer_address_prefix = "2002:db01::/126" + secondary_peer_address_prefix = "2003:db01::/126" + + microsoft_peering { + advertised_public_prefixes = ["2002:db01::/126"] + } + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger) +} + +func testAccAzureRMExpressRouteCircuitPeering_msPeeringIpv6CustomerRouting(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-expressroute-%d" + location = "%s" +} + +resource "azurerm_express_route_circuit" "test" { + name = "acctest-erc-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + service_provider_name = "Equinix" + peering_location = "Silicon Valley" + bandwidth_in_mbps = 50 + + sku { + tier = "Premium" + family = "MeteredData" + } + + tags = { + Env = "Test" + Purpose = "AcceptanceTests" + } +} + +resource "azurerm_express_route_circuit_peering" "test" { + peering_type = "MicrosoftPeering" + express_route_circuit_name = azurerm_express_route_circuit.test.name + resource_group_name = azurerm_resource_group.test.name + peer_asn = 100 + primary_peer_address_prefix = "192.168.3.0/30" + secondary_peer_address_prefix = "192.168.4.0/30" + vlan_id = 300 + + microsoft_peering_config { + advertised_public_prefixes = ["123.2.0.0/24"] + } + ipv6 { + primary_peer_address_prefix = "2002:db01::/126" + secondary_peer_address_prefix = "2003:db01::/126" + + microsoft_peering { + advertised_public_prefixes = ["2002:db01::/126"] + customer_asn = 64511 + routing_registry_name = "ARIN" + } + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger) +} + +func testAccAzureRMExpressRouteCircuitPeering_msPeeringIpv6WithRouteFilter(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-expressroute-%d" + location = "%s" +} + +resource "azurerm_route_filter" "test" { + name = "acctestrf%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + rule { + name = "acctestrule%d" + access = "Allow" + rule_type = "Community" + communities = ["12076:52005", "12076:52006"] + } +} + +resource "azurerm_express_route_circuit" "test" { + name = "acctest-erc-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + service_provider_name = "Equinix" + peering_location = "Silicon Valley" + bandwidth_in_mbps = 50 + + sku { + tier = "Premium" + family = "MeteredData" + } + + tags = { + Env = "Test" + Purpose = "AcceptanceTests" + } +} + +resource "azurerm_express_route_circuit_peering" "test" { + peering_type = "MicrosoftPeering" + express_route_circuit_name = azurerm_express_route_circuit.test.name + resource_group_name = azurerm_resource_group.test.name + peer_asn = 100 + primary_peer_address_prefix = "192.168.3.0/30" + secondary_peer_address_prefix = "192.168.4.0/30" + vlan_id = 300 + route_filter_id = azurerm_route_filter.test.id + + microsoft_peering_config { + advertised_public_prefixes = ["123.2.0.0/24"] + } + + ipv6 { + primary_peer_address_prefix = "2002:db01::/126" + secondary_peer_address_prefix = "2003:db01::/126" + route_filter_id = azurerm_route_filter.test.id + + microsoft_peering { + advertised_public_prefixes = ["2002:db01::/126"] + customer_asn = 64511 + routing_registry_name = "ARIN" + } + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomInteger) +} + func testAccAzureRMExpressRouteCircuitPeering_msPeeringCustomerRouting(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { diff --git a/azurerm/internal/services/network/tests/express_route_circuit_resource_test.go b/azurerm/internal/services/network/tests/express_route_circuit_resource_test.go index a1928728ff3e..43f7f4a6fe6f 100644 --- a/azurerm/internal/services/network/tests/express_route_circuit_resource_test.go +++ b/azurerm/internal/services/network/tests/express_route_circuit_resource_test.go @@ -36,9 +36,12 @@ func TestAccAzureRMExpressRouteCircuit(t *testing.T) { "requiresImport": testAccAzureRMExpressRouteCircuitPeering_requiresImport, }, "MicrosoftPeering": { - "microsoftPeering": testAccAzureRMExpressRouteCircuitPeering_microsoftPeering, - "microsoftPeeringCustomerRouting": testAccAzureRMExpressRouteCircuitPeering_microsoftPeeringCustomerRouting, - "microsoftPeeringWithRouteFilter": testAccAzureRMExpressRouteCircuitPeering_microsoftPeeringWithRouteFilter, + "microsoftPeering": testAccAzureRMExpressRouteCircuitPeering_microsoftPeering, + "microsoftPeeringCustomerRouting": testAccAzureRMExpressRouteCircuitPeering_microsoftPeeringCustomerRouting, + "microsoftPeeringWithRouteFilter": testAccAzureRMExpressRouteCircuitPeering_microsoftPeeringWithRouteFilter, + "microsoftPeeringIpv6": testAccAzureRMExpressRouteCircuitPeering_microsoftPeeringIpv6, + "microsoftPeeringIpv6CustomerRouting": testAccAzureRMExpressRouteCircuitPeering_microsoftPeeringIpv6CustomerRouting, + "microsoftPeeringIpv6WithRouteFilter": testAccAzureRMExpressRouteCircuitPeering_microsoftPeeringIpv6WithRouteFilter, }, "authorization": { "basic": testAccAzureRMExpressRouteCircuitAuthorization_basic, diff --git a/website/docs/r/express_route_circuit_peering.html.markdown b/website/docs/r/express_route_circuit_peering.html.markdown index 7889320f5259..0621343c3af4 100644 --- a/website/docs/r/express_route_circuit_peering.html.markdown +++ b/website/docs/r/express_route_circuit_peering.html.markdown @@ -50,6 +50,15 @@ resource "azurerm_express_route_circuit_peering" "example" { microsoft_peering_config { advertised_public_prefixes = ["123.1.0.0/24"] } + + ipv6 { + primary_peer_address_prefix = "2002:db01::/126" + secondary_peer_address_prefix = "2003:db01::/126" + + microsoft_peering { + advertised_public_prefixes = ["2002:db01::/126"] + } + } } ``` @@ -63,24 +72,57 @@ The following arguments are supported: * `express_route_circuit_name` - (Required) The name of the ExpressRoute Circuit in which to create the Peering. -* `resource_group_name` - (Required) The name of the resource group in which to - create the Express Route Circuit Peering. Changing this forces a new resource to be created. +* `resource_group_name` - (Required) The name of the resource group in which to create the Express Route Circuit Peering. Changing this forces a new resource to be created. * `primary_peer_address_prefix` - (Required) A `/30` subnet for the primary link. + * `secondary_peer_address_prefix` - (Required) A `/30` subnet for the secondary link. + * `vlan_id` - (Required) A valid VLAN ID to establish this peering on. + * `shared_key` - (Optional) The shared key. Can be a maximum of 25 characters. -* `peer_asn` - (Optional) The Either a 16-bit or a 32-bit ASN. Can either be public or private.. + +* `peer_asn` - (Optional) The Either a 16-bit or a 32-bit ASN. Can either be public or private. + * `microsoft_peering_config` - (Optional) A `microsoft_peering_config` block as defined below. Required when `peering_type` is set to `MicrosoftPeering`. + +* `ipv6` - (Optional) A `ipv6` block as defined below. + * `route_filter_id` - (Optional) The ID of the Route Filter. Only available when `peering_type` is set to `MicrosoftPeering`. --- A `microsoft_peering_config` block contains: -* `advertised_public_prefixes` - (Required) A list of Advertised Public Prefixes -* `customer_asn` - (Optional) The CustomerASN of the peering -* `routing_registry_name` - (Optional) The RoutingRegistryName of the configuration +* `advertised_public_prefixes` - (Required) A list of Advertised Public Prefixes. + +* `customer_asn` - (Optional) The CustomerASN of the peering. + +* `routing_registry_name` - (Optional) The Routing Registry against which the AS number and prefixes are registered. For example: `ARIN`, `RIPE`, `AFRINIC` etc. + +--- + +A `ipv6` block contains: + +* `microsoft_peering` - (Required) A `microsoft_peering` block as defined below. + +* `primary_peer_address_prefix` - (Required) A subnet for the primary link. + +* `secondary_peer_address_prefix` - (Required) A subnet for the secondary link. + +* `route_filter_id` - (Optional) The ID of the Route Filter. Only available when `peering_type` is set to `MicrosoftPeering`. + +--- + +A `microsoft_peering` block contains: + +* `advertised_public_prefixes` - (Required) A list of Advertised Public Prefixes. + +* `customer_asn` - (Optional) The CustomerASN of the peering. + +* `routing_registry_name` - (Optional) The Routing Registry against which the AS number and prefixes are registered. For example: `ARIN`, `RIPE`, `AFRINIC` etc. + + ## Attributes Reference @@ -101,8 +143,11 @@ The following attributes are exported: The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: * `create` - (Defaults to 30 minutes) Used when creating the ExpressRoute Circuit Peering. + * `update` - (Defaults to 30 minutes) Used when updating the ExpressRoute Circuit Peering. + * `read` - (Defaults to 5 minutes) Used when retrieving the ExpressRoute Circuit Peering. + * `delete` - (Defaults to 30 minutes) Used when deleting the ExpressRoute Circuit Peering. ## Import