From fadfc5d466e8f125de2e6b1afd3539d634be9218 Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Fri, 30 Oct 2020 15:48:29 +0800 Subject: [PATCH 01/32] New resource azurerm_express_route_connection --- .../services/network/client/client.go | 5 + .../express_route_circuit_peering_resource.go | 32 +- .../express_route_connection_resource.go | 482 ++++++++++++++++++ .../parse/express_route_circuit_peering.go | 35 ++ .../express_route_circuit_peering_test.go | 77 +++ .../network/parse/express_route_connection.go | 50 ++ .../parse/express_route_connection_test.go | 91 ++++ .../network/parse/express_route_gateway.go | 44 ++ .../parse/express_route_gateway_test.go | 86 ++++ .../internal/services/network/registration.go | 1 + .../validate/express_route_circuit_peering.go | 20 + .../express_route_circuit_peering_test.go | 52 ++ .../network/validate/express_route_gateway.go | 20 + .../validate/express_route_gateway_test.go | 42 ++ website/azurerm.erb | 4 + .../r/express_route_connection.html.markdown | 144 ++++++ 16 files changed, 1166 insertions(+), 19 deletions(-) create mode 100644 azurerm/internal/services/network/express_route_connection_resource.go create mode 100644 azurerm/internal/services/network/parse/express_route_circuit_peering.go create mode 100644 azurerm/internal/services/network/parse/express_route_circuit_peering_test.go create mode 100644 azurerm/internal/services/network/parse/express_route_connection.go create mode 100644 azurerm/internal/services/network/parse/express_route_connection_test.go create mode 100644 azurerm/internal/services/network/parse/express_route_gateway.go create mode 100644 azurerm/internal/services/network/parse/express_route_gateway_test.go create mode 100644 azurerm/internal/services/network/validate/express_route_circuit_peering.go create mode 100644 azurerm/internal/services/network/validate/express_route_circuit_peering_test.go create mode 100644 azurerm/internal/services/network/validate/express_route_gateway.go create mode 100644 azurerm/internal/services/network/validate/express_route_gateway_test.go create mode 100644 website/docs/r/express_route_connection.html.markdown diff --git a/azurerm/internal/services/network/client/client.go b/azurerm/internal/services/network/client/client.go index d7012c3406e6..0748317d3a9f 100644 --- a/azurerm/internal/services/network/client/client.go +++ b/azurerm/internal/services/network/client/client.go @@ -17,6 +17,7 @@ type Client struct { ExpressRouteCircuitsClient *network.ExpressRouteCircuitsClient ExpressRouteGatewaysClient *network.ExpressRouteGatewaysClient ExpressRoutePeeringsClient *network.ExpressRouteCircuitPeeringsClient + ExpressRouteConnectionsClient *network.ExpressRouteConnectionsClient FirewallPolicyClient *network.FirewallPoliciesClient HubVirtualNetworkConnectionClient *network.HubVirtualNetworkConnectionsClient InterfacesClient *network.InterfacesClient @@ -80,6 +81,9 @@ func NewClient(o *common.ClientOptions) *Client { ExpressRouteCircuitsClient := network.NewExpressRouteCircuitsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&ExpressRouteCircuitsClient.Client, o.ResourceManagerAuthorizer) + ExpressRouteConnectionsClient := network.NewExpressRouteConnectionsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&ExpressRouteConnectionsClient.Client, o.ResourceManagerAuthorizer) + ExpressRouteGatewaysClient := network.NewExpressRouteGatewaysClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&ExpressRouteGatewaysClient.Client, o.ResourceManagerAuthorizer) @@ -203,6 +207,7 @@ func NewClient(o *common.ClientOptions) *Client { DDOSProtectionPlansClient: &DDOSProtectionPlansClient, ExpressRouteAuthsClient: &ExpressRouteAuthsClient, ExpressRouteCircuitsClient: &ExpressRouteCircuitsClient, + ExpressRouteConnectionsClient: &ExpressRouteConnectionsClient, ExpressRouteGatewaysClient: &ExpressRouteGatewaysClient, ExpressRoutePeeringsClient: &ExpressRoutePeeringsClient, FirewallPolicyClient: &FirewallPolicyClient, 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..e5943a263302 100644 --- a/azurerm/internal/services/network/express_route_circuit_peering_resource.go +++ b/azurerm/internal/services/network/express_route_circuit_peering_resource.go @@ -14,6 +14,7 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/locks" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/parse" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -221,26 +222,23 @@ func resourceArmExpressRouteCircuitPeeringRead(d *schema.ResourceData, meta inte ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) defer cancel() - id, err := azure.ParseAzureResourceID(d.Id()) + id, err := parse.ExpressRouteCircuitPeeringID(d.Id()) if err != nil { return err } - resourceGroup := id.ResourceGroup - circuitName := id.Path["expressRouteCircuits"] - peeringType := id.Path["peerings"] - resp, err := client.Get(ctx, resourceGroup, circuitName, peeringType) + resp, err := client.Get(ctx, id.ResourceGroup, id.CircuitName, id.Name) if err != nil { if utils.ResponseWasNotFound(resp.Response) { d.SetId("") return nil } - return fmt.Errorf("Error making Read request on Express Route Circuit Peering %q (Circuit %q / Resource Group %q): %+v", peeringType, circuitName, resourceGroup, err) + return fmt.Errorf("Error making Read request on Express Route Circuit Peering %q (Circuit %q / Resource Group %q): %+v", id.Name, id.CircuitName, id.ResourceGroup, err) } - d.Set("peering_type", peeringType) - d.Set("express_route_circuit_name", circuitName) - d.Set("resource_group_name", resourceGroup) + d.Set("peering_type", id.Name) + d.Set("express_route_circuit_name", id.CircuitName) + d.Set("resource_group_name", id.ResourceGroup) if props := resp.ExpressRouteCircuitPeeringPropertiesFormat; props != nil { d.Set("azure_asn", props.AzureASN) @@ -271,31 +269,27 @@ func resourceArmExpressRouteCircuitPeeringDelete(d *schema.ResourceData, meta in ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) defer cancel() - id, err := azure.ParseAzureResourceID(d.Id()) + id, err := parse.ExpressRouteCircuitPeeringID(d.Id()) if err != nil { return err } - resourceGroup := id.ResourceGroup - circuitName := id.Path["expressRouteCircuits"] - peeringType := id.Path["peerings"] + locks.ByName(id.CircuitName, expressRouteCircuitResourceName) + defer locks.UnlockByName(id.CircuitName, expressRouteCircuitResourceName) - locks.ByName(circuitName, expressRouteCircuitResourceName) - defer locks.UnlockByName(circuitName, expressRouteCircuitResourceName) - - future, err := client.Delete(ctx, resourceGroup, circuitName, peeringType) + future, err := client.Delete(ctx, id.ResourceGroup, id.CircuitName, id.Name) if err != nil { if response.WasNotFound(future.Response()) { return nil } - return fmt.Errorf("Error issuing delete request for Express Route Circuit Peering %q (Circuit %q / Resource Group %q): %+v", peeringType, circuitName, resourceGroup, err) + return fmt.Errorf("Error issuing delete request for Express Route Circuit Peering %q (Circuit %q / Resource Group %q): %+v", id.Name, id.CircuitName, id.ResourceGroup, err) } if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { if response.WasNotFound(future.Response()) { return nil } - return fmt.Errorf("Error waiting for Express Route Circuit Peering %q (Circuit %q / Resource Group %q) to be deleted: %+v", peeringType, circuitName, resourceGroup, err) + return fmt.Errorf("Error waiting for Express Route Circuit Peering %q (Circuit %q / Resource Group %q) to be deleted: %+v", id.Name, id.CircuitName, id.ResourceGroup, err) } return err diff --git a/azurerm/internal/services/network/express_route_connection_resource.go b/azurerm/internal/services/network/express_route_connection_resource.go new file mode 100644 index 000000000000..f0bd9bccf2fc --- /dev/null +++ b/azurerm/internal/services/network/express_route_connection_resource.go @@ -0,0 +1,482 @@ +package network + +import ( + "fmt" + "log" + "time" + + "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2020-05-01/network" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/parse" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/validate" + azSchema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmExpressRouteConnection() *schema.Resource { + return &schema.Resource{ + Create: resourceArmExpressRouteConnectionCreateUpdate, + Read: resourceArmExpressRouteConnectionRead, + Update: resourceArmExpressRouteConnectionCreateUpdate, + Delete: resourceArmExpressRouteConnectionDelete, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Importer: azSchema.ValidateResourceIDPriorToImport(func(id string) error { + _, err := parse.ExpressRouteConnectionID(id) + return err + }), + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "express_route_gateway_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.ExpressRouteGatewayID, + }, + + "express_route_circuit_peering_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.ExpressRouteCircuitPeeringID, + }, + + "authorization_key": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "enable_internet_security": { + Type: schema.TypeBool, + Optional: true, + }, + + "routing": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "associated_route_table_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: azure.ValidateResourceID, + }, + + "propagated_route_table": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "labels": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringIsNotEmpty, + }, + }, + + "route_table_ids": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: azure.ValidateResourceID, + }, + }, + }, + }, + }, + + "static_vnet_route": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "address_prefixes": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.IsCIDR, + }, + }, + + "next_hop_ip_address": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.IsIPv4Address, + }, + }, + }, + }, + }, + }, + }, + + "routing_weight": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntBetween(0, 32000), + }, + }, + } +} +func resourceArmExpressRouteConnectionCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Network.ExpressRouteConnectionsClient + ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) + defer cancel() + + name := d.Get("name").(string) + + id, err := parse.ExpressRouteGatewayID(d.Get("express_route_gateway_id").(string)) + if err != nil { + return err + } + + if d.IsNewResource() { + existing, err := client.Get(ctx, id.ResourceGroup, id.Name, name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("checking for present of existing ExpressRouteConnection %q (Resource Group %q / expressRouteGatewayName %q): %+v", name, id.ResourceGroup, id.Name, err) + } + } + + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("azurerm_network_express_route_connection", *existing.ID) + } + } + + putExpressRouteConnectionParameters := network.ExpressRouteConnection{ + Name: utils.String(d.Get("name").(string)), + ExpressRouteConnectionProperties: &network.ExpressRouteConnectionProperties{ + ExpressRouteCircuitPeering: &network.ExpressRouteCircuitPeeringID{ + ID: utils.String(d.Get("express_route_circuit_peering_id").(string)), + }, + }, + } + + if v, ok := d.GetOk("authorization_key"); ok { + putExpressRouteConnectionParameters.ExpressRouteConnectionProperties.AuthorizationKey = utils.String(v.(string)) + } + + if v, ok := d.GetOk("enable_internet_security"); ok { + putExpressRouteConnectionParameters.ExpressRouteConnectionProperties.EnableInternetSecurity = utils.Bool(v.(bool)) + } + + if v, ok := d.GetOk("routing_weight"); ok { + putExpressRouteConnectionParameters.ExpressRouteConnectionProperties.RoutingWeight = utils.Int32(int32(v.(int))) + } + + if v, ok := d.GetOk("routing"); ok { + putExpressRouteConnectionParameters.ExpressRouteConnectionProperties.RoutingConfiguration = expandArmExpressRouteConnectionRouting(v.([]interface{})) + } + + future, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.Name, name, putExpressRouteConnectionParameters) + if err != nil { + return fmt.Errorf("creating/updating ExpressRouteConnection %q (Resource Group %q / expressRouteGatewayName %q): %+v", name, id.ResourceGroup, id.Name, err) + } + + if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting on creating/updating future for ExpressRouteConnection %q (Resource Group %q / expressRouteGatewayName %q): %+v", name, id.ResourceGroup, id.Name, err) + } + + resp, err := client.Get(ctx, id.ResourceGroup, id.Name, name) + if err != nil { + return fmt.Errorf("retrieving ExpressRouteConnection %q (Resource Group %q / expressRouteGatewayName %q): %+v", name, id.ResourceGroup, id.Name, err) + } + + if resp.ID == nil || *resp.ID == "" { + return fmt.Errorf("empty or nil ID returned for ExpressRouteConnection %q (Resource Group %q / expressRouteGatewayName %q) ID", name, id.ResourceGroup, id.Name) + } + + d.SetId(*resp.ID) + + return resourceArmExpressRouteConnectionRead(d, meta) +} + +func resourceArmExpressRouteConnectionRead(d *schema.ResourceData, meta interface{}) error { + subscriptionId := meta.(*clients.Client).Account.SubscriptionId + client := meta.(*clients.Client).Network.ExpressRouteConnectionsClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.ExpressRouteConnectionID(d.Id()) + if err != nil { + return err + } + + resp, err := client.Get(ctx, id.ResourceGroup, id.ExpressRouteGatewayName, id.Name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[INFO] network %q does not exist - removing from state", d.Id()) + d.SetId("") + return nil + } + + return fmt.Errorf("retrieving Network ExpressRouteConnection %q (Resource Group %q / expressRouteGatewayName %q): %+v", id.Name, id.ResourceGroup, id.ExpressRouteGatewayName, err) + } + + d.Set("name", id.Name) + d.Set("expresss_route_gateway_id", parse.NewExpressRouteGatewayID(id.ResourceGroup, id.Name).ID(subscriptionId)) + + if props := resp.ExpressRouteConnectionProperties; props != nil { + if v := props.ExpressRouteCircuitPeering; v != nil { + d.Set("express_route_circuit_peering_id", v.ID) + } + + if v := props.AuthorizationKey; v != nil { + d.Set("authorization_key", v) + } + + if v := props.EnableInternetSecurity; v != nil { + d.Set("enable_internet_security", v) + } + + if v := props.RoutingWeight; v != nil { + d.Set("routing_weight", v) + } + + if err := d.Set("routing", flattenArmExpressRouteConnectionRouting(props.RoutingConfiguration)); err != nil { + return fmt.Errorf("setting `routing`: %+v", err) + } + } + + return nil +} + +func resourceArmExpressRouteConnectionDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Network.ExpressRouteConnectionsClient + ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.ExpressRouteConnectionID(d.Id()) + if err != nil { + return err + } + + future, err := client.Delete(ctx, id.ResourceGroup, id.ExpressRouteGatewayName, id.Name) + if err != nil { + return fmt.Errorf("deleting ExpressRouteConnection %q (Resource Group %q / expressRouteGatewayName %q): %+v", id.Name, id.ResourceGroup, id.ExpressRouteGatewayName, err) + } + + if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting on deleting future for ExpressRouteConnection %q (Resource Group %q / expressRouteGatewayName %q): %+v", id.Name, id.ResourceGroup, id.ExpressRouteGatewayName, err) + } + + return nil +} + +func expandArmExpressRouteConnectionRouting(input []interface{}) *network.RoutingConfiguration { + if len(input) == 0 { + return &network.RoutingConfiguration{} + } + + v := input[0].(map[string]interface{}) + result := network.RoutingConfiguration{} + + if associatedRouteTableId := v["associated_route_table_id"].(string); associatedRouteTableId != "" { + result.AssociatedRouteTable = &network.SubResource{ + ID: utils.String(associatedRouteTableId), + } + } + + if vnetStaticRoute := v["static_vnet_route"].([]interface{}); len(vnetStaticRoute) != 0 { + result.VnetRoutes = expandArmExpressRouteConnectionStaticVnetRoute(vnetStaticRoute) + } + + if propagatedRouteTable := v["propagated_route_table"].([]interface{}); len(propagatedRouteTable) != 0 { + result.PropagatedRouteTables = expandArmExpressRouteConnectionPropagatedRouteTable(propagatedRouteTable) + } + + return &result +} + +func expandArmExpressRouteConnectionPropagatedRouteTable(input []interface{}) *network.PropagatedRouteTable { + if len(input) == 0 { + return &network.PropagatedRouteTable{} + } + + v := input[0].(map[string]interface{}) + + result := network.PropagatedRouteTable{} + + if labels := v["labels"].(*schema.Set).List(); len(labels) != 0 { + result.Labels = utils.ExpandStringSlice(labels) + } + + if routeTableIds := v["route_table_ids"].([]interface{}); len(routeTableIds) != 0 { + result.Ids = expandIDsToSubResources(routeTableIds) + } + + return &result +} + +func expandArmExpressRouteConnectionStaticVnetRoute(input []interface{}) *network.VnetRoute { + if len(input) == 0 { + return &network.VnetRoute{} + } + + results := make([]network.StaticRoute, 0) + + for _, item := range input { + v := item.(map[string]interface{}) + + result := network.StaticRoute{} + + if name := v["name"].(string); name != "" { + result.Name = utils.String(name) + } + + if addressPrefixes := v["address_prefixes"].(*schema.Set).List(); len(addressPrefixes) != 0 { + result.AddressPrefixes = utils.ExpandStringSlice(addressPrefixes) + } + + if nextHopIPAddress := v["next_hop_ip_address"].(string); nextHopIPAddress != "" { + result.NextHopIPAddress = utils.String(nextHopIPAddress) + } + + results = append(results, result) + } + + return &network.VnetRoute{ + StaticRoutes: &results, + } +} + +func expandIDsToSubResources(input []interface{}) *[]network.SubResource { + ids := make([]network.SubResource, 0) + + for _, v := range input { + ids = append(ids, network.SubResource{ + ID: utils.String(v.(string)), + }) + } + + return &ids +} + +func flattenArmExpressRouteConnectionRouting(input *network.RoutingConfiguration) []interface{} { + if input == nil { + return []interface{}{} + } + + associatedRouteTableId := "" + if input.AssociatedRouteTable != nil && input.AssociatedRouteTable.ID != nil { + associatedRouteTableId = *input.AssociatedRouteTable.ID + } + + return []interface{}{ + map[string]interface{}{ + "associated_route_table_id": associatedRouteTableId, + "propagated_route_table": flattenArmExpressRouteConnectionPropagatedRouteTable(input.PropagatedRouteTables), + "static_vnet_route": flattenArmExpressRouteConnectionStaticVnetRoute(input.VnetRoutes), + }, + } +} + +func flattenArmExpressRouteConnectionPropagatedRouteTable(input *network.PropagatedRouteTable) []interface{} { + if input == nil { + return make([]interface{}, 0) + } + + labels := make([]interface{}, 0) + if input.Labels != nil { + labels = utils.FlattenStringSlice(input.Labels) + } + + routeTableIds := make([]interface{}, 0) + if input.Ids != nil { + routeTableIds = flattenSubResourcesToIDs(input.Ids) + } + + return []interface{}{ + map[string]interface{}{ + "labels": labels, + "route_table_ids": routeTableIds, + }, + } +} + +func flattenArmExpressRouteConnectionStaticVnetRoute(input *network.VnetRoute) []interface{} { + results := make([]interface{}, 0) + if input == nil || input.StaticRoutes == nil { + return results + } + + for _, item := range *input.StaticRoutes { + var name string + if item.Name != nil { + name = *item.Name + } + + var nextHopIpAddress string + if item.NextHopIPAddress != nil { + nextHopIpAddress = *item.NextHopIPAddress + } + + addressPrefixes := make([]interface{}, 0) + if item.AddressPrefixes != nil { + addressPrefixes = utils.FlattenStringSlice(item.AddressPrefixes) + } + + v := map[string]interface{}{ + "name": name, + "address_prefixes": addressPrefixes, + "next_hop_ip_address": nextHopIpAddress, + } + + results = append(results, v) + } + + return results +} + +func flattenSubResourcesToIDs(input *[]network.SubResource) []interface{} { + ids := make([]interface{}, 0) + if input == nil { + return ids + } + + for _, v := range *input { + if v.ID == nil { + continue + } + + ids = append(ids, *v.ID) + } + + return ids +} diff --git a/azurerm/internal/services/network/parse/express_route_circuit_peering.go b/azurerm/internal/services/network/parse/express_route_circuit_peering.go new file mode 100644 index 000000000000..9f08ecbf6c3d --- /dev/null +++ b/azurerm/internal/services/network/parse/express_route_circuit_peering.go @@ -0,0 +1,35 @@ +package parse + +import ( + "fmt" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" +) + +type ExpressRouteCircuitPeeringId struct { + ResourceGroup string + CircuitName string + Name string +} + +func ExpressRouteCircuitPeeringID(input string) (*ExpressRouteCircuitPeeringId, error) { + id, err := azure.ParseAzureResourceID(input) + if err != nil { + return nil, fmt.Errorf("parsing expressRouteCircuitPeering ID %q: %+v", input, err) + } + + expressRouteCircuitPeering := ExpressRouteCircuitPeeringId{ + ResourceGroup: id.ResourceGroup, + } + if expressRouteCircuitPeering.CircuitName, err = id.PopSegment("expressRouteCircuits"); err != nil { + return nil, err + } + if expressRouteCircuitPeering.Name, err = id.PopSegment("peerings"); err != nil { + return nil, err + } + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &expressRouteCircuitPeering, nil +} diff --git a/azurerm/internal/services/network/parse/express_route_circuit_peering_test.go b/azurerm/internal/services/network/parse/express_route_circuit_peering_test.go new file mode 100644 index 000000000000..bb672c8db120 --- /dev/null +++ b/azurerm/internal/services/network/parse/express_route_circuit_peering_test.go @@ -0,0 +1,77 @@ +package parse + +import ( + "testing" +) + +func TestExpressRouteCircuitPeeringID(t *testing.T) { + testData := []struct { + Name string + Input string + Expected *ExpressRouteCircuitPeeringId + }{ + { + Name: "Empty", + Input: "", + Expected: nil, + }, + { + Name: "No Resource Groups Segment", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000", + Expected: nil, + }, + { + Name: "No Resource Groups Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/", + Expected: nil, + }, + { + Name: "Resource Group ID", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/", + Expected: nil, + }, + { + Name: "Missing Peering Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup1/providers/Microsoft.Network/expressRouteCircuits/circuit1/peerings", + Expected: nil, + }, + { + Name: "network ExpressRouteCircuitPeering ID", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup1/providers/Microsoft.Network/expressRouteCircuits/circuit1/peerings/peering1", + Expected: &ExpressRouteCircuitPeeringId{ + ResourceGroup: "resourceGroup1", + CircuitName: "circuit1", + Name: "peering1", + }, + }, + { + Name: "Wrong Casing", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup1/providers/Microsoft.Network/expressRouteCircuits/circuit1/Peerings/peering1", + Expected: nil, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q..", v.Name) + + actual, err := ExpressRouteCircuitPeeringID(v.Input) + if err != nil { + if v.Expected == nil { + continue + } + t.Fatalf("Expected a value but got an error: %s", err) + } + + if actual.ResourceGroup != v.Expected.ResourceGroup { + t.Fatalf("Expected %q but got %q for ResourceGroup", v.Expected.ResourceGroup, actual.ResourceGroup) + } + + if actual.CircuitName != v.Expected.CircuitName { + t.Fatalf("Expected %q but got %q for CircuitName", v.Expected.CircuitName, actual.CircuitName) + } + + if actual.Name != v.Expected.Name { + t.Fatalf("Expected %q but got %q for Name", v.Expected.Name, actual.Name) + } + } +} diff --git a/azurerm/internal/services/network/parse/express_route_connection.go b/azurerm/internal/services/network/parse/express_route_connection.go new file mode 100644 index 000000000000..ac6eedfd0251 --- /dev/null +++ b/azurerm/internal/services/network/parse/express_route_connection.go @@ -0,0 +1,50 @@ +package parse + +import ( + "fmt" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" +) + +type ExpressRouteConnectionId struct { + ResourceGroup string + ExpressRouteGatewayName string + Name string +} + +func NewExpressRouteConnectionID(resourceGroup string, expressRouteGatewayName string, name string) ExpressRouteConnectionId { + return ExpressRouteConnectionId{ + ResourceGroup: resourceGroup, + ExpressRouteGatewayName: expressRouteGatewayName, + Name: name, + } +} + +func (id ExpressRouteConnectionId) ID(subscriptionId string) string { + return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/expressRouteGateways/%s/expressRouteConnections/%s", subscriptionId, id.ResourceGroup, id.ExpressRouteGatewayName, id.Name) +} + +func ExpressRouteConnectionID(input string) (*ExpressRouteConnectionId, error) { + id, err := azure.ParseAzureResourceID(input) + if err != nil { + return nil, fmt.Errorf("parsing expressRouteConnection ID %q: %+v", input, err) + } + + expressRouteConnection := ExpressRouteConnectionId{ + ResourceGroup: id.ResourceGroup, + } + + if expressRouteConnection.ExpressRouteGatewayName, err = id.PopSegment("expressRouteGateways"); err != nil { + return nil, err + } + + if expressRouteConnection.Name, err = id.PopSegment("expressRouteConnections"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &expressRouteConnection, nil +} diff --git a/azurerm/internal/services/network/parse/express_route_connection_test.go b/azurerm/internal/services/network/parse/express_route_connection_test.go new file mode 100644 index 000000000000..9abde6de539c --- /dev/null +++ b/azurerm/internal/services/network/parse/express_route_connection_test.go @@ -0,0 +1,91 @@ +package parse + +import ( + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/resourceid" + "testing" +) + +var _ resourceid.Formatter = ExpressRouteConnectionId{} + +func TestExpressRouteConnectionIDFormatter(t *testing.T) { + subscriptionId := "12345678-1234-5678-1234-123456789012" + id := NewExpressRouteConnectionID("resourceGroup1", "expressRouteGateway1", "connection1") + actual := id.ID(subscriptionId) + expected := "/subscriptions/12345678-1234-5678-1234-123456789012/resourceGroups/resourceGroup1/providers/Microsoft.Network/expressRouteGateways/expressRouteGateway1/expressRouteConnections/connection1" + + if actual != expected { + t.Fatalf("Expected %q but got %q", expected, actual) + } +} + +func TestExpressRouteConnectionID(t *testing.T) { + testData := []struct { + Name string + Input string + Expected *ExpressRouteConnectionId + }{ + { + Name: "Empty", + Input: "", + Expected: nil, + }, + { + Name: "No Resource Groups Segment", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000", + Expected: nil, + }, + { + Name: "No Resource Groups Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/", + Expected: nil, + }, + { + Name: "Resource Group ID", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/", + Expected: nil, + }, + { + Name: "Missing ExpressRouteConnection Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup1/providers/Microsoft.Network/expressRouteGateways/expressRouteGateway1/expressRouteConnections", + Expected: nil, + }, + { + Name: "network ExpressRouteConnection ID", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup1/providers/Microsoft.Network/expressRouteGateways/expressRouteGateway1/expressRouteConnections/connection1", + Expected: &ExpressRouteConnectionId{ + ResourceGroup: "resourceGroup1", + ExpressRouteGatewayName: "expressRouteGateway1", + Name: "connection1", + }, + }, + { + Name: "Wrong Casing", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup1/providers/Microsoft.Network/expressRouteGateways/expressRouteGateway1/ExpressRouteConnections/connection1", + Expected: nil, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q..", v.Name) + + actual, err := ExpressRouteConnectionID(v.Input) + if err != nil { + if v.Expected == nil { + continue + } + t.Fatalf("Expected a value but got an error: %s", err) + } + + if actual.ResourceGroup != v.Expected.ResourceGroup { + t.Fatalf("Expected %q but got %q for ResourceGroup", v.Expected.ResourceGroup, actual.ResourceGroup) + } + + if actual.ExpressRouteGatewayName != v.Expected.ExpressRouteGatewayName { + t.Fatalf("Expected %q but got %q for ExpressRouteGatewayName", v.Expected.ExpressRouteGatewayName, actual.ExpressRouteGatewayName) + } + + if actual.Name != v.Expected.Name { + t.Fatalf("Expected %q but got %q for Name", v.Expected.Name, actual.Name) + } + } +} diff --git a/azurerm/internal/services/network/parse/express_route_gateway.go b/azurerm/internal/services/network/parse/express_route_gateway.go new file mode 100644 index 000000000000..a353621afce8 --- /dev/null +++ b/azurerm/internal/services/network/parse/express_route_gateway.go @@ -0,0 +1,44 @@ +package parse + +import ( + "fmt" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" +) + +type ExpressRouteGatewayId struct { + ResourceGroup string + Name string +} + +func NewExpressRouteGatewayID(resourceGroup string, name string) ExpressRouteGatewayId { + return ExpressRouteGatewayId{ + ResourceGroup: resourceGroup, + Name: name, + } +} + +func (id ExpressRouteGatewayId) ID(subscriptionId string) string { + return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/expressRouteGateways/%s", subscriptionId, id.ResourceGroup, id.Name) +} + +func ExpressRouteGatewayID(input string) (*ExpressRouteGatewayId, error) { + id, err := azure.ParseAzureResourceID(input) + if err != nil { + return nil, fmt.Errorf("parsing networkExpressRouteGateway ID %q: %+v", input, err) + } + + expressRouteGateway := ExpressRouteGatewayId{ + ResourceGroup: id.ResourceGroup, + } + + if expressRouteGateway.Name, err = id.PopSegment("expressRouteGateways"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &expressRouteGateway, nil +} diff --git a/azurerm/internal/services/network/parse/express_route_gateway_test.go b/azurerm/internal/services/network/parse/express_route_gateway_test.go new file mode 100644 index 000000000000..eb703dd46acd --- /dev/null +++ b/azurerm/internal/services/network/parse/express_route_gateway_test.go @@ -0,0 +1,86 @@ +package parse + +import ( + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/resourceid" + "testing" +) + +var _ resourceid.Formatter = ExpressRouteGatewayId{} + +func TestExpressRouteGatewayIDFormatter(t *testing.T) { + subscriptionId := "12345678-1234-5678-1234-123456789012" + id := NewExpressRouteGatewayID("resourceGroup1", "expressRouteGateway1") + actual := id.ID(subscriptionId) + expected := "/subscriptions/12345678-1234-5678-1234-123456789012/resourceGroups/resourceGroup1/providers/Microsoft.Network/expressRouteGateways/expressRouteGateway1" + + if actual != expected { + t.Fatalf("Expected %q but got %q", expected, actual) + } +} + +func TestExpressRouteGatewayID(t *testing.T) { + testData := []struct { + Name string + Input string + Expected *ExpressRouteGatewayId + }{ + { + Name: "Empty", + Input: "", + Expected: nil, + }, + { + Name: "No Resource Groups Segment", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000", + Expected: nil, + }, + { + Name: "No Resource Groups Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/", + Expected: nil, + }, + { + Name: "Resource Group ID", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/", + Expected: nil, + }, + { + Name: "Missing ExpressRouteGateway Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup1/providers/Microsoft.Network/expressRouteGateways", + Expected: nil, + }, + { + Name: "network ExpressRouteGateway ID", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup1/providers/Microsoft.Network/expressRouteGateways/expressRouteGateway1", + Expected: &ExpressRouteGatewayId{ + ResourceGroup: "resourceGroup1", + Name: "expressRouteGateway1", + }, + }, + { + Name: "Wrong Casing", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup1/providers/Microsoft.Network/ExpressRouteGateways/expressRouteGateway1", + Expected: nil, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q..", v.Name) + + actual, err := ExpressRouteGatewayID(v.Input) + if err != nil { + if v.Expected == nil { + continue + } + t.Fatalf("Expected a value but got an error: %s", err) + } + + if actual.ResourceGroup != v.Expected.ResourceGroup { + t.Fatalf("Expected %q but got %q for ResourceGroup", v.Expected.ResourceGroup, actual.ResourceGroup) + } + + if actual.Name != v.Expected.Name { + t.Fatalf("Expected %q but got %q for Name", v.Expected.Name, actual.Name) + } + } +} diff --git a/azurerm/internal/services/network/registration.go b/azurerm/internal/services/network/registration.go index 9d5fbf82c84e..e40dae544423 100644 --- a/azurerm/internal/services/network/registration.go +++ b/azurerm/internal/services/network/registration.go @@ -62,6 +62,7 @@ func (r Registration) SupportedResources() map[string]*schema.Resource { "azurerm_express_route_circuit_authorization": resourceArmExpressRouteCircuitAuthorization(), "azurerm_express_route_circuit_peering": resourceArmExpressRouteCircuitPeering(), "azurerm_express_route_circuit": resourceArmExpressRouteCircuit(), + "azurerm_express_route_connection": resourceArmExpressRouteConnection(), "azurerm_express_route_gateway": resourceArmExpressRouteGateway(), "azurerm_firewall_application_rule_collection": resourceArmFirewallApplicationRuleCollection(), "azurerm_firewall_policy": resourceArmFirewallPolicy(), diff --git a/azurerm/internal/services/network/validate/express_route_circuit_peering.go b/azurerm/internal/services/network/validate/express_route_circuit_peering.go new file mode 100644 index 000000000000..0c2b30f0eaa7 --- /dev/null +++ b/azurerm/internal/services/network/validate/express_route_circuit_peering.go @@ -0,0 +1,20 @@ +package validate + +import ( + "fmt" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/parse" +) + +func ExpressRouteCircuitPeeringID(i interface{}, k string) (warnings []string, errors []error) { + v, ok := i.(string) + if !ok { + return nil, []error{fmt.Errorf("expected type of %q to be string", k)} + } + + if _, err := parse.ExpressRouteCircuitPeeringID(v); err != nil { + return nil, []error{err} + } + + return nil, nil +} diff --git a/azurerm/internal/services/network/validate/express_route_circuit_peering_test.go b/azurerm/internal/services/network/validate/express_route_circuit_peering_test.go new file mode 100644 index 000000000000..a13a95a54333 --- /dev/null +++ b/azurerm/internal/services/network/validate/express_route_circuit_peering_test.go @@ -0,0 +1,52 @@ +package validate + +import "testing" + +func TestExpressRouteCircuitPeeringID(t *testing.T) { + testData := []struct { + Name string + Input string + Valid bool + }{ + { + Name: "Empty", + Input: "", + Valid: false, + }, + { + Name: "No expressRouteCircuits Segment", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo", + Valid: false, + }, + { + Name: "No expressRouteCircuits Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/expressRouteCircuits/", + Valid: false, + }, + { + Name: "No peerings Segment", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/expressRouteCircuits/circuit1", + Valid: false, + }, + { + Name: "No peerings Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/expressRouteCircuits/circuit1/peerings/", + Valid: false, + }, + { + Name: "Completed", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/expressRouteCircuits/circuit1/peerings/peering1", + Valid: true, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Input) + + _, errors := ExpressRouteCircuitPeeringID(v.Input, "express_route_circuit_peering_id") + isValid := len(errors) == 0 + if v.Valid != isValid { + t.Fatalf("Expected %t but got %t", v.Valid, isValid) + } + } +} diff --git a/azurerm/internal/services/network/validate/express_route_gateway.go b/azurerm/internal/services/network/validate/express_route_gateway.go new file mode 100644 index 000000000000..e0f63847fd95 --- /dev/null +++ b/azurerm/internal/services/network/validate/express_route_gateway.go @@ -0,0 +1,20 @@ +package validate + +import ( + "fmt" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/parse" +) + +func ExpressRouteGatewayID(i interface{}, k string) (warnings []string, errors []error) { + v, ok := i.(string) + if !ok { + return nil, []error{fmt.Errorf("expected type of %q to be string", k)} + } + + if _, err := parse.ExpressRouteGatewayID(v); err != nil { + return nil, []error{err} + } + + return nil, nil +} diff --git a/azurerm/internal/services/network/validate/express_route_gateway_test.go b/azurerm/internal/services/network/validate/express_route_gateway_test.go new file mode 100644 index 000000000000..d3ef3b9a5fae --- /dev/null +++ b/azurerm/internal/services/network/validate/express_route_gateway_test.go @@ -0,0 +1,42 @@ +package validate + +import "testing" + +func TestExpressRouteGatewayID(t *testing.T) { + testData := []struct { + Name string + Input string + Valid bool + }{ + { + Name: "Empty", + Input: "", + Valid: false, + }, + { + Name: "No expressRouteGateways Segment", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo", + Valid: false, + }, + { + Name: "No expressRouteGateways Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/expressRouteGateways/", + Valid: false, + }, + { + Name: "Completed", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/expressRouteGateways/expressRouteGateway1", + Valid: true, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Input) + + _, errors := ExpressRouteGatewayID(v.Input, "express_route_gateway_id") + isValid := len(errors) == 0 + if v.Valid != isValid { + t.Fatalf("Expected %t but got %t", v.Valid, isValid) + } + } +} diff --git a/website/azurerm.erb b/website/azurerm.erb index dabe8cc541d5..f22823054bdb 100644 --- a/website/azurerm.erb +++ b/website/azurerm.erb @@ -2329,6 +2329,10 @@ azurerm_express_route_circuit_peering +
  • + azurerm_express_route_connection +
  • +
  • azurerm_express_route_gateway
  • diff --git a/website/docs/r/express_route_connection.html.markdown b/website/docs/r/express_route_connection.html.markdown new file mode 100644 index 000000000000..78d31464db87 --- /dev/null +++ b/website/docs/r/express_route_connection.html.markdown @@ -0,0 +1,144 @@ +--- +subcategory: "Network" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_express_route_connection" +description: |- + Manages an Express Route Connection. +--- + +# azurerm_express_route_connection + +Manages an Express Route Connection. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "example" { + name = "example-resources" + location = "West Europe" +} + +resource "azurerm_virtual_wan" "example" { + name = "example-vwan" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location +} + +resource "azurerm_virtual_hub" "example" { + name = "example-vhub" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + virtual_wan_id = azurerm_virtual_wan.example.id + address_prefix = "10.0.1.0/24" +} + +resource "azurerm_express_route_gateway" "example" { + name = "example-expressroutegateway" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + virtual_hub_id = azurerm_virtual_hub.example.id + scale_units = 1 +} + +resource "azurerm_express_route_circuit" "example" { + name = "example-expressroutecircuit" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + service_provider_name = "Equinix" + peering_location = "Silicon Valley" + bandwidth_in_mbps = 50 + + sku { + tier = "Standard" + family = "MeteredData" + } + + allow_classic_operations = false +} + +resource "azurerm_express_route_circuit_peering" "example" { + peering_type = "AzurePrivatePeering" + express_route_circuit_name = azurerm_express_route_circuit.example.name + resource_group_name = azurerm_resource_group.example.name + shared_key = "SSSSsssssshhhhhItsASecret" + peer_asn = 100 + primary_peer_address_prefix = "192.168.1.0/30" + secondary_peer_address_prefix = "192.168.2.0/30" + vlan_id = 100 +} + +resource "azurerm_express_route_connection" "example" { + name = "example-expressrouteconn" + express_route_gateway_id = azurerm_express_route_gateway.example.id + express_route_circuit_peering_id = azurerm_express_route_circuit_peering.example.id +} +``` + +## Arguments Reference + +The following arguments are supported: + +* `name` - (Required) The name which should be used for this Express Route Connection. Changing this forces a new resource to be created. + +* `express_route_gateway_id` - (Required) The ID of the Express Route Gateway within which this connection should be created. Changing this forces a new resource to be created. + +* `express_route_circuit_peering_id` - (Required) The ID of the Express Route Circuit Peering within which this connection should be created. Changing this forces a new resource to be created. + +* `authorization_key` - (Optional) The authorization key to establish the connection. + +* `enable_internet_security` - (Optional) Enable internet security. + +* `routing` - (Optional) A `routing` block as defined below. + +* `routing_weight` - (Optional) The routing weight associated to the connection. Must be between `0` and `32000`. + +--- + +A `routing` block supports the following: + +* `associated_route_table_id` - (Optional) The ID of the route table associated with this Virtual Hub connection. + +* `propagated_route_table` - (Optional) A `propagated_route_table` block as defined below. + +* `static_vnet_route` - (Optional) A `static_vnet_route` block as defined below. + +--- + +A `propagated_route_table` block supports the following: + +* `labels` - (Optional) The list of labels to assign to this route table. + +* `route_table_ids` - (Optional) A list of Route Table ID's to associated with this Virtual Hub Connection. + +--- + +A `static_vnet_route` block supports the following: + +* `name` - (Optional) The name which should be used for this Static Route. + +* `address_prefixes` - (Optional) A list of CIDR Ranges which should be used as Address Prefixes. + +* `next_hop_ip_address` - (Optional) The IP Address which should be used for the Next Hop. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the Express Route Connection. + +## Timeouts + +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 Express Route Connection. +* `read` - (Defaults to 5 minutes) Used when retrieving the Express Route Connection. +* `update` - (Defaults to 30 minutes) Used when updating the Express Route Connection. +* `delete` - (Defaults to 30 minutes) Used when deleting the Express Route Connection. + +## Import + +Express Route Connections can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_express_route_connection.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.Network/expressRouteGateways/expressRouteGateway1/expressRouteConnections/connection1 +``` \ No newline at end of file From 54372baf84ac5343670f4879b002540defd0db02 Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Fri, 12 Mar 2021 19:47:25 +0800 Subject: [PATCH 02/32] update code --- .../express_route_connection_resource.go | 56 ++++----- .../network/parse/express_route_connection.go | 45 ++++++-- .../parse/express_route_connection_test.go | 107 ++++++++++++------ .../internal/services/network/resourceids.go | 3 + .../validate/express_route_connection_id.go | 23 ++++ .../express_route_connection_id_test.go | 88 ++++++++++++++ .../r/express_route_connection.html.markdown | 12 +- 7 files changed, 250 insertions(+), 84 deletions(-) create mode 100644 azurerm/internal/services/network/validate/express_route_connection_id.go create mode 100644 azurerm/internal/services/network/validate/express_route_connection_id_test.go diff --git a/azurerm/internal/services/network/express_route_connection_resource.go b/azurerm/internal/services/network/express_route_connection_resource.go index 8ea0baf6c1cf..e3b3873f49a6 100644 --- a/azurerm/internal/services/network/express_route_connection_resource.go +++ b/azurerm/internal/services/network/express_route_connection_resource.go @@ -2,13 +2,11 @@ package network import ( "fmt" - "log" "time" "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2020-05-01/network" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/helper/validation" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/parse" @@ -81,7 +79,7 @@ func resourceArmExpressRouteConnection() *schema.Resource { Type: schema.TypeString, Optional: true, Computed: true, - ValidateFunc: azure.ValidateResourceID, + ValidateFunc: validate.HubRouteTableID, }, "propagated_route_table": { @@ -107,7 +105,7 @@ func resourceArmExpressRouteConnection() *schema.Resource { Computed: true, Elem: &schema.Schema{ Type: schema.TypeString, - ValidateFunc: azure.ValidateResourceID, + ValidateFunc: validate.HubRouteTableID, }, }, }, @@ -155,31 +153,34 @@ func resourceArmExpressRouteConnection() *schema.Resource { } } func resourceArmExpressRouteConnectionCreateUpdate(d *schema.ResourceData, meta interface{}) error { + subscriptionId := meta.(*clients.Client).Account.SubscriptionId client := meta.(*clients.Client).Network.ExpressRouteConnectionsClient ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) defer cancel() name := d.Get("name").(string) - - id, err := parse.ExpressRouteGatewayID(d.Get("express_route_gateway_id").(string)) + resourceGroup := d.Get("resource_group_name").(string) + expressRouteGatewayId, err := parse.ExpressRouteGatewayID(d.Get("express_route_gateway_id").(string)) if err != nil { return err } + id := parse.NewExpressRouteConnectionID(subscriptionId, resourceGroup, expressRouteGatewayId.Name, name) + if d.IsNewResource() { - existing, err := client.Get(ctx, id.ResourceGroup, id.Name, name) + existing, err := client.Get(ctx, resourceGroup, expressRouteCircuitResourceName, name) if err != nil { if !utils.ResponseWasNotFound(existing.Response) { - return fmt.Errorf("checking for present of existing ExpressRouteConnection %q (Resource Group %q / expressRouteGatewayName %q): %+v", name, id.ResourceGroup, id.Name, err) + return fmt.Errorf("checking for existing %s: %+v", id, err) } } if existing.ID != nil && *existing.ID != "" { - return tf.ImportAsExistsError("azurerm_network_express_route_connection", *existing.ID) + return tf.ImportAsExistsError("azurerm_express_route_connection", id.ID()) } } - putExpressRouteConnectionParameters := network.ExpressRouteConnection{ + expressRouteConnectionParameters := network.ExpressRouteConnection{ Name: utils.String(d.Get("name").(string)), ExpressRouteConnectionProperties: &network.ExpressRouteConnectionProperties{ ExpressRouteCircuitPeering: &network.ExpressRouteCircuitPeeringID{ @@ -189,40 +190,31 @@ func resourceArmExpressRouteConnectionCreateUpdate(d *schema.ResourceData, meta } if v, ok := d.GetOk("authorization_key"); ok { - putExpressRouteConnectionParameters.ExpressRouteConnectionProperties.AuthorizationKey = utils.String(v.(string)) + expressRouteConnectionParameters.ExpressRouteConnectionProperties.AuthorizationKey = utils.String(v.(string)) } if v, ok := d.GetOk("enable_internet_security"); ok { - putExpressRouteConnectionParameters.ExpressRouteConnectionProperties.EnableInternetSecurity = utils.Bool(v.(bool)) + expressRouteConnectionParameters.ExpressRouteConnectionProperties.EnableInternetSecurity = utils.Bool(v.(bool)) } if v, ok := d.GetOk("routing_weight"); ok { - putExpressRouteConnectionParameters.ExpressRouteConnectionProperties.RoutingWeight = utils.Int32(int32(v.(int))) + expressRouteConnectionParameters.ExpressRouteConnectionProperties.RoutingWeight = utils.Int32(int32(v.(int))) } if v, ok := d.GetOk("routing"); ok { - putExpressRouteConnectionParameters.ExpressRouteConnectionProperties.RoutingConfiguration = expandArmExpressRouteConnectionRouting(v.([]interface{})) + expressRouteConnectionParameters.ExpressRouteConnectionProperties.RoutingConfiguration = expandArmExpressRouteConnectionRouting(v.([]interface{})) } - future, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.Name, name, putExpressRouteConnectionParameters) + future, err := client.CreateOrUpdate(ctx, resourceGroup, expressRouteGatewayId.Name, name, expressRouteConnectionParameters) if err != nil { - return fmt.Errorf("creating/updating ExpressRouteConnection %q (Resource Group %q / expressRouteGatewayName %q): %+v", name, id.ResourceGroup, id.Name, err) + return fmt.Errorf("creating/updating %s: %+v", id, err) } if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { - return fmt.Errorf("waiting on creating/updating future for ExpressRouteConnection %q (Resource Group %q / expressRouteGatewayName %q): %+v", name, id.ResourceGroup, id.Name, err) + return fmt.Errorf("waiting for creation/update of %s: %+v", id, err) } - resp, err := client.Get(ctx, id.ResourceGroup, id.Name, name) - if err != nil { - return fmt.Errorf("retrieving ExpressRouteConnection %q (Resource Group %q / expressRouteGatewayName %q): %+v", name, id.ResourceGroup, id.Name, err) - } - - if resp.ID == nil || *resp.ID == "" { - return fmt.Errorf("empty or nil ID returned for ExpressRouteConnection %q (Resource Group %q / expressRouteGatewayName %q) ID", name, id.ResourceGroup, id.Name) - } - - d.SetId(*resp.ID) + d.SetId(id.ID()) return resourceArmExpressRouteConnectionRead(d, meta) } @@ -241,16 +233,14 @@ func resourceArmExpressRouteConnectionRead(d *schema.ResourceData, meta interfac resp, err := client.Get(ctx, id.ResourceGroup, id.ExpressRouteGatewayName, id.Name) if err != nil { if utils.ResponseWasNotFound(resp.Response) { - log.Printf("[INFO] network %q does not exist - removing from state", d.Id()) d.SetId("") return nil } - - return fmt.Errorf("retrieving Network ExpressRouteConnection %q (Resource Group %q / expressRouteGatewayName %q): %+v", id.Name, id.ResourceGroup, id.ExpressRouteGatewayName, err) + return fmt.Errorf("retrieving %s: %+v", *id, err) } d.Set("name", id.Name) - d.Set("expresss_route_gateway_id", parse.NewExpressRouteGatewayID(id.ResourceGroup, id.Name).ID(subscriptionId)) + d.Set("express_route_gateway_id", parse.NewExpressRouteGatewayID(id.ResourceGroup, id.ExpressRouteGatewayName).ID(subscriptionId)) if props := resp.ExpressRouteConnectionProperties; props != nil { if v := props.ExpressRouteCircuitPeering; v != nil { @@ -289,11 +279,11 @@ func resourceArmExpressRouteConnectionDelete(d *schema.ResourceData, meta interf future, err := client.Delete(ctx, id.ResourceGroup, id.ExpressRouteGatewayName, id.Name) if err != nil { - return fmt.Errorf("deleting ExpressRouteConnection %q (Resource Group %q / expressRouteGatewayName %q): %+v", id.Name, id.ResourceGroup, id.ExpressRouteGatewayName, err) + return fmt.Errorf("deleting %s: %+v", *id, err) } if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { - return fmt.Errorf("waiting on deleting future for ExpressRouteConnection %q (Resource Group %q / expressRouteGatewayName %q): %+v", id.Name, id.ResourceGroup, id.ExpressRouteGatewayName, err) + return fmt.Errorf("waiting for deletion of %s: %+v", *id, err) } return nil diff --git a/azurerm/internal/services/network/parse/express_route_connection.go b/azurerm/internal/services/network/parse/express_route_connection.go index ac6eedfd0251..3ffd2f65972b 100644 --- a/azurerm/internal/services/network/parse/express_route_connection.go +++ b/azurerm/internal/services/network/parse/express_route_connection.go @@ -1,44 +1,69 @@ package parse +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + import ( "fmt" + "strings" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" ) type ExpressRouteConnectionId struct { + SubscriptionId string ResourceGroup string ExpressRouteGatewayName string Name string } -func NewExpressRouteConnectionID(resourceGroup string, expressRouteGatewayName string, name string) ExpressRouteConnectionId { +func NewExpressRouteConnectionID(subscriptionId, resourceGroup, expressRouteGatewayName, name string) ExpressRouteConnectionId { return ExpressRouteConnectionId{ + SubscriptionId: subscriptionId, ResourceGroup: resourceGroup, ExpressRouteGatewayName: expressRouteGatewayName, Name: name, } } -func (id ExpressRouteConnectionId) ID(subscriptionId string) string { - return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/expressRouteGateways/%s/expressRouteConnections/%s", subscriptionId, id.ResourceGroup, id.ExpressRouteGatewayName, id.Name) +func (id ExpressRouteConnectionId) String() string { + segments := []string{ + fmt.Sprintf("Name %q", id.Name), + fmt.Sprintf("Express Route Gateway Name %q", id.ExpressRouteGatewayName), + fmt.Sprintf("Resource Group %q", id.ResourceGroup), + } + segmentsStr := strings.Join(segments, " / ") + return fmt.Sprintf("%s: (%s)", "Express Route Connection", segmentsStr) } +func (id ExpressRouteConnectionId) ID() string { + fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/expressRouteGateways/%s/expressRouteConnections/%s" + return fmt.Sprintf(fmtString, id.SubscriptionId, id.ResourceGroup, id.ExpressRouteGatewayName, id.Name) +} + +// ExpressRouteConnectionID parses a ExpressRouteConnection ID into an ExpressRouteConnectionId struct func ExpressRouteConnectionID(input string) (*ExpressRouteConnectionId, error) { id, err := azure.ParseAzureResourceID(input) if err != nil { - return nil, fmt.Errorf("parsing expressRouteConnection ID %q: %+v", input, err) + return nil, err } - expressRouteConnection := ExpressRouteConnectionId{ - ResourceGroup: id.ResourceGroup, + resourceId := ExpressRouteConnectionId{ + SubscriptionId: id.SubscriptionID, + ResourceGroup: id.ResourceGroup, } - if expressRouteConnection.ExpressRouteGatewayName, err = id.PopSegment("expressRouteGateways"); err != nil { - return nil, err + if resourceId.SubscriptionId == "" { + return nil, fmt.Errorf("ID was missing the 'subscriptions' element") + } + + if resourceId.ResourceGroup == "" { + return nil, fmt.Errorf("ID was missing the 'resourceGroups' element") } - if expressRouteConnection.Name, err = id.PopSegment("expressRouteConnections"); err != nil { + if resourceId.ExpressRouteGatewayName, err = id.PopSegment("expressRouteGateways"); err != nil { + return nil, err + } + if resourceId.Name, err = id.PopSegment("expressRouteConnections"); err != nil { return nil, err } @@ -46,5 +71,5 @@ func ExpressRouteConnectionID(input string) (*ExpressRouteConnectionId, error) { return nil, err } - return &expressRouteConnection, nil + return &resourceId, nil } diff --git a/azurerm/internal/services/network/parse/express_route_connection_test.go b/azurerm/internal/services/network/parse/express_route_connection_test.go index 9abde6de539c..30e902fe5c72 100644 --- a/azurerm/internal/services/network/parse/express_route_connection_test.go +++ b/azurerm/internal/services/network/parse/express_route_connection_test.go @@ -1,18 +1,18 @@ package parse +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + import ( - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/resourceid" "testing" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/resourceid" ) var _ resourceid.Formatter = ExpressRouteConnectionId{} func TestExpressRouteConnectionIDFormatter(t *testing.T) { - subscriptionId := "12345678-1234-5678-1234-123456789012" - id := NewExpressRouteConnectionID("resourceGroup1", "expressRouteGateway1", "connection1") - actual := id.ID(subscriptionId) - expected := "/subscriptions/12345678-1234-5678-1234-123456789012/resourceGroups/resourceGroup1/providers/Microsoft.Network/expressRouteGateways/expressRouteGateway1/expressRouteConnections/connection1" - + actual := NewExpressRouteConnectionID("12345678-1234-9876-4563-123456789012", "resGroup1", "ergw1", "erConnection1").ID() + expected := "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteGateways/ergw1/expressRouteConnections/erConnection1" if actual != expected { t.Fatalf("Expected %q but got %q", expected, actual) } @@ -20,70 +20,107 @@ func TestExpressRouteConnectionIDFormatter(t *testing.T) { func TestExpressRouteConnectionID(t *testing.T) { testData := []struct { - Name string Input string + Error bool Expected *ExpressRouteConnectionId }{ + { - Name: "Empty", - Input: "", - Expected: nil, + // empty + Input: "", + Error: true, }, + { - Name: "No Resource Groups Segment", - Input: "/subscriptions/00000000-0000-0000-0000-000000000000", - Expected: nil, + // missing SubscriptionId + Input: "/", + Error: true, }, + { - Name: "No Resource Groups Value", - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/", - Expected: nil, + // missing value for SubscriptionId + Input: "/subscriptions/", + Error: true, }, + { - Name: "Resource Group ID", - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/", - Expected: nil, + // missing ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", + Error: true, }, + { - Name: "Missing ExpressRouteConnection Value", - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup1/providers/Microsoft.Network/expressRouteGateways/expressRouteGateway1/expressRouteConnections", - Expected: nil, + // missing value for ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", + Error: true, }, + { - Name: "network ExpressRouteConnection ID", - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup1/providers/Microsoft.Network/expressRouteGateways/expressRouteGateway1/expressRouteConnections/connection1", + // missing ExpressRouteGatewayName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/", + Error: true, + }, + + { + // missing value for ExpressRouteGatewayName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteGateways/", + Error: true, + }, + + { + // missing Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteGateways/ergw1/", + Error: true, + }, + + { + // missing value for Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteGateways/ergw1/expressRouteConnections/", + Error: true, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteGateways/ergw1/expressRouteConnections/erConnection1", Expected: &ExpressRouteConnectionId{ - ResourceGroup: "resourceGroup1", - ExpressRouteGatewayName: "expressRouteGateway1", - Name: "connection1", + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resGroup1", + ExpressRouteGatewayName: "ergw1", + Name: "erConnection1", }, }, + { - Name: "Wrong Casing", - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup1/providers/Microsoft.Network/expressRouteGateways/expressRouteGateway1/ExpressRouteConnections/connection1", - Expected: nil, + // upper-cased + Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.NETWORK/EXPRESSROUTEGATEWAYS/ERGW1/EXPRESSROUTECONNECTIONS/ERCONNECTION1", + Error: true, }, } for _, v := range testData { - t.Logf("[DEBUG] Testing %q..", v.Name) + t.Logf("[DEBUG] Testing %q", v.Input) actual, err := ExpressRouteConnectionID(v.Input) if err != nil { - if v.Expected == nil { + if v.Error { continue } - t.Fatalf("Expected a value but got an error: %s", err) + + t.Fatalf("Expect a value but got an error: %s", err) + } + if v.Error { + t.Fatal("Expect an error but didn't get one") } + if actual.SubscriptionId != v.Expected.SubscriptionId { + t.Fatalf("Expected %q but got %q for SubscriptionId", v.Expected.SubscriptionId, actual.SubscriptionId) + } if actual.ResourceGroup != v.Expected.ResourceGroup { t.Fatalf("Expected %q but got %q for ResourceGroup", v.Expected.ResourceGroup, actual.ResourceGroup) } - if actual.ExpressRouteGatewayName != v.Expected.ExpressRouteGatewayName { t.Fatalf("Expected %q but got %q for ExpressRouteGatewayName", v.Expected.ExpressRouteGatewayName, actual.ExpressRouteGatewayName) } - if actual.Name != v.Expected.Name { t.Fatalf("Expected %q but got %q for Name", v.Expected.Name, actual.Name) } diff --git a/azurerm/internal/services/network/resourceids.go b/azurerm/internal/services/network/resourceids.go index 965dc1514633..e5294b4e229a 100644 --- a/azurerm/internal/services/network/resourceids.go +++ b/azurerm/internal/services/network/resourceids.go @@ -53,3 +53,6 @@ package network // Virtual Network Gateway //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=VirtualNetworkGateway -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/virtualNetworkGateways/gw1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=VirtualNetworkGatewayIpConfiguration -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/virtualNetworkGateways/gw1/ipConfigurations/cfg1 + +// Express Route Connection +//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=ExpressRouteConnection -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteGateways/ergw1/expressRouteConnections/erConnection1 diff --git a/azurerm/internal/services/network/validate/express_route_connection_id.go b/azurerm/internal/services/network/validate/express_route_connection_id.go new file mode 100644 index 000000000000..2ca191dd2d5d --- /dev/null +++ b/azurerm/internal/services/network/validate/express_route_connection_id.go @@ -0,0 +1,23 @@ +package validate + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "fmt" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/parse" +) + +func ExpressRouteConnectionID(input interface{}, key string) (warnings []string, errors []error) { + v, ok := input.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected %q to be a string", key)) + return + } + + if _, err := parse.ExpressRouteConnectionID(v); err != nil { + errors = append(errors, err) + } + + return +} diff --git a/azurerm/internal/services/network/validate/express_route_connection_id_test.go b/azurerm/internal/services/network/validate/express_route_connection_id_test.go new file mode 100644 index 000000000000..aebf1e14a24a --- /dev/null +++ b/azurerm/internal/services/network/validate/express_route_connection_id_test.go @@ -0,0 +1,88 @@ +package validate + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import "testing" + +func TestExpressRouteConnectionID(t *testing.T) { + cases := []struct { + Input string + Valid bool + }{ + + { + // empty + Input: "", + Valid: false, + }, + + { + // missing SubscriptionId + Input: "/", + Valid: false, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Valid: false, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", + Valid: false, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", + Valid: false, + }, + + { + // missing ExpressRouteGatewayName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/", + Valid: false, + }, + + { + // missing value for ExpressRouteGatewayName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteGateways/", + Valid: false, + }, + + { + // missing Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteGateways/ergw1/", + Valid: false, + }, + + { + // missing value for Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteGateways/ergw1/expressRouteConnections/", + Valid: false, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteGateways/ergw1/expressRouteConnections/erConnection1", + Valid: true, + }, + + { + // upper-cased + Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.NETWORK/EXPRESSROUTEGATEWAYS/ERGW1/EXPRESSROUTECONNECTIONS/ERCONNECTION1", + Valid: false, + }, + } + for _, tc := range cases { + t.Logf("[DEBUG] Testing Value %s", tc.Input) + _, errors := ExpressRouteConnectionID(tc.Input, "test") + valid := len(errors) == 0 + + if tc.Valid != valid { + t.Fatalf("Expected %t but got %t", tc.Valid, valid) + } + } +} diff --git a/website/docs/r/express_route_connection.html.markdown b/website/docs/r/express_route_connection.html.markdown index 78d31464db87..3adc6f65e707 100644 --- a/website/docs/r/express_route_connection.html.markdown +++ b/website/docs/r/express_route_connection.html.markdown @@ -44,8 +44,8 @@ resource "azurerm_express_route_circuit" "example" { name = "example-expressroutecircuit" location = azurerm_resource_group.example.location resource_group_name = azurerm_resource_group.example.name - service_provider_name = "Equinix" - peering_location = "Silicon Valley" + service_provider_name = "Microsoft ER Test" + peering_location = "Area51" bandwidth_in_mbps = 50 sku { @@ -60,7 +60,7 @@ resource "azurerm_express_route_circuit_peering" "example" { peering_type = "AzurePrivatePeering" express_route_circuit_name = azurerm_express_route_circuit.example.name resource_group_name = azurerm_resource_group.example.name - shared_key = "SSSSsssssshhhhhItsASecret" + shared_key = "ItsASecret" peer_asn = 100 primary_peer_address_prefix = "192.168.1.0/30" secondary_peer_address_prefix = "192.168.2.0/30" @@ -80,10 +80,10 @@ The following arguments are supported: * `name` - (Required) The name which should be used for this Express Route Connection. Changing this forces a new resource to be created. -* `express_route_gateway_id` - (Required) The ID of the Express Route Gateway within which this connection should be created. Changing this forces a new resource to be created. - * `express_route_circuit_peering_id` - (Required) The ID of the Express Route Circuit Peering within which this connection should be created. Changing this forces a new resource to be created. +* `express_route_gateway_id` - (Required) The ID of the Express Route Gateway within which this connection should be created. Changing this forces a new resource to be created. + * `authorization_key` - (Optional) The authorization key to establish the connection. * `enable_internet_security` - (Optional) Enable internet security. @@ -141,4 +141,4 @@ Express Route Connections can be imported using the `resource id`, e.g. ```shell terraform import azurerm_express_route_connection.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.Network/expressRouteGateways/expressRouteGateway1/expressRouteConnections/connection1 -``` \ No newline at end of file +``` From 9ea329706aa82f701cc88c68a3bdf7086662c145 Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Fri, 12 Mar 2021 20:12:35 +0800 Subject: [PATCH 03/32] update code --- .../express_route_circuit_peering_resource.go | 18 +-- .../express_route_connection_resource.go | 6 +- .../parse/express_route_circuit_peering.go | 58 +++++++-- .../express_route_circuit_peering_test.go | 117 +++++++++++++----- .../network/parse/express_route_gateway.go | 49 ++++++-- .../parse/express_route_gateway_test.go | 92 +++++++++----- .../internal/services/network/resourceids.go | 2 + .../validate/express_route_circuit_peering.go | 20 --- .../express_route_circuit_peering_id.go | 23 ++++ .../express_route_circuit_peering_id_test.go | 88 +++++++++++++ .../express_route_circuit_peering_test.go | 52 -------- .../network/validate/express_route_gateway.go | 20 --- .../validate/express_route_gateway_id.go | 23 ++++ .../validate/express_route_gateway_id_test.go | 76 ++++++++++++ .../validate/express_route_gateway_test.go | 42 ------- 15 files changed, 452 insertions(+), 234 deletions(-) delete mode 100644 azurerm/internal/services/network/validate/express_route_circuit_peering.go create mode 100644 azurerm/internal/services/network/validate/express_route_circuit_peering_id.go create mode 100644 azurerm/internal/services/network/validate/express_route_circuit_peering_id_test.go delete mode 100644 azurerm/internal/services/network/validate/express_route_circuit_peering_test.go delete mode 100644 azurerm/internal/services/network/validate/express_route_gateway.go create mode 100644 azurerm/internal/services/network/validate/express_route_gateway_id.go create mode 100644 azurerm/internal/services/network/validate/express_route_gateway_id_test.go delete mode 100644 azurerm/internal/services/network/validate/express_route_gateway_test.go 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 3e7b34a13a48..f898d55859aa 100644 --- a/azurerm/internal/services/network/express_route_circuit_peering_resource.go +++ b/azurerm/internal/services/network/express_route_circuit_peering_resource.go @@ -300,17 +300,17 @@ func resourceExpressRouteCircuitPeeringRead(d *schema.ResourceData, meta interfa return err } - resp, err := client.Get(ctx, id.ResourceGroup, id.CircuitName, id.Name) + resp, err := client.Get(ctx, id.ResourceGroup, id.ExpressRouteCircuitName, id.PeeringName) if err != nil { if utils.ResponseWasNotFound(resp.Response) { d.SetId("") return nil } - return fmt.Errorf("Error making Read request on Express Route Circuit Peering %q (Circuit %q / Resource Group %q): %+v", id.Name, id.CircuitName, id.ResourceGroup, err) + return fmt.Errorf("Error making Read request on Express Route Circuit Peering %q (Circuit %q / Resource Group %q): %+v", id.PeeringName, id.ExpressRouteCircuitName, id.ResourceGroup, err) } - d.Set("peering_type", id.Name) - d.Set("express_route_circuit_name", id.CircuitName) + d.Set("peering_type", id.PeeringName) + d.Set("express_route_circuit_name", id.ExpressRouteCircuitName) d.Set("resource_group_name", id.ResourceGroup) if props := resp.ExpressRouteCircuitPeeringPropertiesFormat; props != nil { @@ -350,22 +350,22 @@ func resourceExpressRouteCircuitPeeringDelete(d *schema.ResourceData, meta inter return err } - locks.ByName(id.CircuitName, expressRouteCircuitResourceName) - defer locks.UnlockByName(id.CircuitName, expressRouteCircuitResourceName) + locks.ByName(id.ExpressRouteCircuitName, expressRouteCircuitResourceName) + defer locks.UnlockByName(id.ExpressRouteCircuitName, expressRouteCircuitResourceName) - future, err := client.Delete(ctx, id.ResourceGroup, id.CircuitName, id.Name) + future, err := client.Delete(ctx, id.ResourceGroup, id.ExpressRouteCircuitName, id.PeeringName) if err != nil { if response.WasNotFound(future.Response()) { return nil } - return fmt.Errorf("Error issuing delete request for Express Route Circuit Peering %q (Circuit %q / Resource Group %q): %+v", id.Name, id.CircuitName, id.ResourceGroup, err) + return fmt.Errorf("Error issuing delete request for Express Route Circuit Peering %q (Circuit %q / Resource Group %q): %+v", id.PeeringName, id.ExpressRouteCircuitName, id.ResourceGroup, err) } if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { if response.WasNotFound(future.Response()) { return nil } - return fmt.Errorf("Error waiting for Express Route Circuit Peering %q (Circuit %q / Resource Group %q) to be deleted: %+v", id.Name, id.CircuitName, id.ResourceGroup, err) + return fmt.Errorf("Error waiting for Express Route Circuit Peering %q (Circuit %q / Resource Group %q) to be deleted: %+v", id.PeeringName, id.ExpressRouteCircuitName, id.ResourceGroup, err) } return err diff --git a/azurerm/internal/services/network/express_route_connection_resource.go b/azurerm/internal/services/network/express_route_connection_resource.go index e3b3873f49a6..1d8ff609eb9f 100644 --- a/azurerm/internal/services/network/express_route_connection_resource.go +++ b/azurerm/internal/services/network/express_route_connection_resource.go @@ -153,7 +153,6 @@ func resourceArmExpressRouteConnection() *schema.Resource { } } func resourceArmExpressRouteConnectionCreateUpdate(d *schema.ResourceData, meta interface{}) error { - subscriptionId := meta.(*clients.Client).Account.SubscriptionId client := meta.(*clients.Client).Network.ExpressRouteConnectionsClient ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) defer cancel() @@ -165,7 +164,7 @@ func resourceArmExpressRouteConnectionCreateUpdate(d *schema.ResourceData, meta return err } - id := parse.NewExpressRouteConnectionID(subscriptionId, resourceGroup, expressRouteGatewayId.Name, name) + id := parse.NewExpressRouteConnectionID(expressRouteGatewayId.SubscriptionId, expressRouteGatewayId.ResourceGroup, expressRouteGatewayId.Name, name) if d.IsNewResource() { existing, err := client.Get(ctx, resourceGroup, expressRouteCircuitResourceName, name) @@ -220,7 +219,6 @@ func resourceArmExpressRouteConnectionCreateUpdate(d *schema.ResourceData, meta } func resourceArmExpressRouteConnectionRead(d *schema.ResourceData, meta interface{}) error { - subscriptionId := meta.(*clients.Client).Account.SubscriptionId client := meta.(*clients.Client).Network.ExpressRouteConnectionsClient ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) defer cancel() @@ -240,7 +238,7 @@ func resourceArmExpressRouteConnectionRead(d *schema.ResourceData, meta interfac } d.Set("name", id.Name) - d.Set("express_route_gateway_id", parse.NewExpressRouteGatewayID(id.ResourceGroup, id.ExpressRouteGatewayName).ID(subscriptionId)) + d.Set("express_route_gateway_id", parse.NewExpressRouteGatewayID(id.SubscriptionId, id.ResourceGroup, id.ExpressRouteGatewayName).ID()) if props := resp.ExpressRouteConnectionProperties; props != nil { if v := props.ExpressRouteCircuitPeering; v != nil { diff --git a/azurerm/internal/services/network/parse/express_route_circuit_peering.go b/azurerm/internal/services/network/parse/express_route_circuit_peering.go index 9f08ecbf6c3d..eb55c135d97e 100644 --- a/azurerm/internal/services/network/parse/express_route_circuit_peering.go +++ b/azurerm/internal/services/network/parse/express_route_circuit_peering.go @@ -1,35 +1,75 @@ package parse +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + import ( "fmt" + "strings" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" ) type ExpressRouteCircuitPeeringId struct { - ResourceGroup string - CircuitName string - Name string + SubscriptionId string + ResourceGroup string + ExpressRouteCircuitName string + PeeringName string +} + +func NewExpressRouteCircuitPeeringID(subscriptionId, resourceGroup, expressRouteCircuitName, peeringName string) ExpressRouteCircuitPeeringId { + return ExpressRouteCircuitPeeringId{ + SubscriptionId: subscriptionId, + ResourceGroup: resourceGroup, + ExpressRouteCircuitName: expressRouteCircuitName, + PeeringName: peeringName, + } +} + +func (id ExpressRouteCircuitPeeringId) String() string { + segments := []string{ + fmt.Sprintf("Peering Name %q", id.PeeringName), + fmt.Sprintf("Express Route Circuit Name %q", id.ExpressRouteCircuitName), + fmt.Sprintf("Resource Group %q", id.ResourceGroup), + } + segmentsStr := strings.Join(segments, " / ") + return fmt.Sprintf("%s: (%s)", "Express Route Circuit Peering", segmentsStr) +} + +func (id ExpressRouteCircuitPeeringId) ID() string { + fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/expressRouteCircuits/%s/peerings/%s" + return fmt.Sprintf(fmtString, id.SubscriptionId, id.ResourceGroup, id.ExpressRouteCircuitName, id.PeeringName) } +// ExpressRouteCircuitPeeringID parses a ExpressRouteCircuitPeering ID into an ExpressRouteCircuitPeeringId struct func ExpressRouteCircuitPeeringID(input string) (*ExpressRouteCircuitPeeringId, error) { id, err := azure.ParseAzureResourceID(input) if err != nil { - return nil, fmt.Errorf("parsing expressRouteCircuitPeering ID %q: %+v", input, err) + return nil, err + } + + resourceId := ExpressRouteCircuitPeeringId{ + SubscriptionId: id.SubscriptionID, + ResourceGroup: id.ResourceGroup, } - expressRouteCircuitPeering := ExpressRouteCircuitPeeringId{ - ResourceGroup: id.ResourceGroup, + if resourceId.SubscriptionId == "" { + return nil, fmt.Errorf("ID was missing the 'subscriptions' element") } - if expressRouteCircuitPeering.CircuitName, err = id.PopSegment("expressRouteCircuits"); err != nil { + + if resourceId.ResourceGroup == "" { + return nil, fmt.Errorf("ID was missing the 'resourceGroups' element") + } + + if resourceId.ExpressRouteCircuitName, err = id.PopSegment("expressRouteCircuits"); err != nil { return nil, err } - if expressRouteCircuitPeering.Name, err = id.PopSegment("peerings"); err != nil { + if resourceId.PeeringName, err = id.PopSegment("peerings"); err != nil { return nil, err } + if err := id.ValidateNoEmptySegments(input); err != nil { return nil, err } - return &expressRouteCircuitPeering, nil + return &resourceId, nil } diff --git a/azurerm/internal/services/network/parse/express_route_circuit_peering_test.go b/azurerm/internal/services/network/parse/express_route_circuit_peering_test.go index bb672c8db120..39b00756af4b 100644 --- a/azurerm/internal/services/network/parse/express_route_circuit_peering_test.go +++ b/azurerm/internal/services/network/parse/express_route_circuit_peering_test.go @@ -1,77 +1,128 @@ package parse +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + import ( "testing" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/resourceid" ) +var _ resourceid.Formatter = ExpressRouteCircuitPeeringId{} + +func TestExpressRouteCircuitPeeringIDFormatter(t *testing.T) { + actual := NewExpressRouteCircuitPeeringID("12345678-1234-9876-4563-123456789012", "resGroup1", "erCircuit1", "peering1").ID() + expected := "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteCircuits/erCircuit1/peerings/peering1" + if actual != expected { + t.Fatalf("Expected %q but got %q", expected, actual) + } +} + func TestExpressRouteCircuitPeeringID(t *testing.T) { testData := []struct { - Name string Input string + Error bool Expected *ExpressRouteCircuitPeeringId }{ + + { + // empty + Input: "", + Error: true, + }, + + { + // missing SubscriptionId + Input: "/", + Error: true, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Error: true, + }, + { - Name: "Empty", - Input: "", - Expected: nil, + // missing ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", + Error: true, }, + { - Name: "No Resource Groups Segment", - Input: "/subscriptions/00000000-0000-0000-0000-000000000000", - Expected: nil, + // missing value for ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", + Error: true, }, + { - Name: "No Resource Groups Value", - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/", - Expected: nil, + // missing ExpressRouteCircuitName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/", + Error: true, }, + { - Name: "Resource Group ID", - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/", - Expected: nil, + // missing value for ExpressRouteCircuitName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteCircuits/", + Error: true, }, + { - Name: "Missing Peering Value", - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup1/providers/Microsoft.Network/expressRouteCircuits/circuit1/peerings", - Expected: nil, + // missing PeeringName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteCircuits/erCircuit1/", + Error: true, }, + { - Name: "network ExpressRouteCircuitPeering ID", - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup1/providers/Microsoft.Network/expressRouteCircuits/circuit1/peerings/peering1", + // missing value for PeeringName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteCircuits/erCircuit1/peerings/", + Error: true, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteCircuits/erCircuit1/peerings/peering1", Expected: &ExpressRouteCircuitPeeringId{ - ResourceGroup: "resourceGroup1", - CircuitName: "circuit1", - Name: "peering1", + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resGroup1", + ExpressRouteCircuitName: "erCircuit1", + PeeringName: "peering1", }, }, + { - Name: "Wrong Casing", - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup1/providers/Microsoft.Network/expressRouteCircuits/circuit1/Peerings/peering1", - Expected: nil, + // upper-cased + Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.NETWORK/EXPRESSROUTECIRCUITS/ERCIRCUIT1/PEERINGS/PEERING1", + Error: true, }, } for _, v := range testData { - t.Logf("[DEBUG] Testing %q..", v.Name) + t.Logf("[DEBUG] Testing %q", v.Input) actual, err := ExpressRouteCircuitPeeringID(v.Input) if err != nil { - if v.Expected == nil { + if v.Error { continue } - t.Fatalf("Expected a value but got an error: %s", err) + + t.Fatalf("Expect a value but got an error: %s", err) + } + if v.Error { + t.Fatal("Expect an error but didn't get one") } + if actual.SubscriptionId != v.Expected.SubscriptionId { + t.Fatalf("Expected %q but got %q for SubscriptionId", v.Expected.SubscriptionId, actual.SubscriptionId) + } if actual.ResourceGroup != v.Expected.ResourceGroup { t.Fatalf("Expected %q but got %q for ResourceGroup", v.Expected.ResourceGroup, actual.ResourceGroup) } - - if actual.CircuitName != v.Expected.CircuitName { - t.Fatalf("Expected %q but got %q for CircuitName", v.Expected.CircuitName, actual.CircuitName) + if actual.ExpressRouteCircuitName != v.Expected.ExpressRouteCircuitName { + t.Fatalf("Expected %q but got %q for ExpressRouteCircuitName", v.Expected.ExpressRouteCircuitName, actual.ExpressRouteCircuitName) } - - if actual.Name != v.Expected.Name { - t.Fatalf("Expected %q but got %q for Name", v.Expected.Name, actual.Name) + if actual.PeeringName != v.Expected.PeeringName { + t.Fatalf("Expected %q but got %q for PeeringName", v.Expected.PeeringName, actual.PeeringName) } } } diff --git a/azurerm/internal/services/network/parse/express_route_gateway.go b/azurerm/internal/services/network/parse/express_route_gateway.go index a353621afce8..c32da2f41fbf 100644 --- a/azurerm/internal/services/network/parse/express_route_gateway.go +++ b/azurerm/internal/services/network/parse/express_route_gateway.go @@ -1,38 +1,63 @@ package parse +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + import ( "fmt" + "strings" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" ) type ExpressRouteGatewayId struct { - ResourceGroup string - Name string + SubscriptionId string + ResourceGroup string + Name string } -func NewExpressRouteGatewayID(resourceGroup string, name string) ExpressRouteGatewayId { +func NewExpressRouteGatewayID(subscriptionId, resourceGroup, name string) ExpressRouteGatewayId { return ExpressRouteGatewayId{ - ResourceGroup: resourceGroup, - Name: name, + SubscriptionId: subscriptionId, + ResourceGroup: resourceGroup, + Name: name, } } -func (id ExpressRouteGatewayId) ID(subscriptionId string) string { - return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/expressRouteGateways/%s", subscriptionId, id.ResourceGroup, id.Name) +func (id ExpressRouteGatewayId) String() string { + segments := []string{ + fmt.Sprintf("Name %q", id.Name), + fmt.Sprintf("Resource Group %q", id.ResourceGroup), + } + segmentsStr := strings.Join(segments, " / ") + return fmt.Sprintf("%s: (%s)", "Express Route Gateway", segmentsStr) } +func (id ExpressRouteGatewayId) ID() string { + fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/expressRouteGateways/%s" + return fmt.Sprintf(fmtString, id.SubscriptionId, id.ResourceGroup, id.Name) +} + +// ExpressRouteGatewayID parses a ExpressRouteGateway ID into an ExpressRouteGatewayId struct func ExpressRouteGatewayID(input string) (*ExpressRouteGatewayId, error) { id, err := azure.ParseAzureResourceID(input) if err != nil { - return nil, fmt.Errorf("parsing networkExpressRouteGateway ID %q: %+v", input, err) + return nil, err + } + + resourceId := ExpressRouteGatewayId{ + SubscriptionId: id.SubscriptionID, + ResourceGroup: id.ResourceGroup, + } + + if resourceId.SubscriptionId == "" { + return nil, fmt.Errorf("ID was missing the 'subscriptions' element") } - expressRouteGateway := ExpressRouteGatewayId{ - ResourceGroup: id.ResourceGroup, + if resourceId.ResourceGroup == "" { + return nil, fmt.Errorf("ID was missing the 'resourceGroups' element") } - if expressRouteGateway.Name, err = id.PopSegment("expressRouteGateways"); err != nil { + if resourceId.Name, err = id.PopSegment("expressRouteGateways"); err != nil { return nil, err } @@ -40,5 +65,5 @@ func ExpressRouteGatewayID(input string) (*ExpressRouteGatewayId, error) { return nil, err } - return &expressRouteGateway, nil + return &resourceId, nil } diff --git a/azurerm/internal/services/network/parse/express_route_gateway_test.go b/azurerm/internal/services/network/parse/express_route_gateway_test.go index eb703dd46acd..4e06c9110f3c 100644 --- a/azurerm/internal/services/network/parse/express_route_gateway_test.go +++ b/azurerm/internal/services/network/parse/express_route_gateway_test.go @@ -1,18 +1,18 @@ package parse +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + import ( - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/resourceid" "testing" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/resourceid" ) var _ resourceid.Formatter = ExpressRouteGatewayId{} func TestExpressRouteGatewayIDFormatter(t *testing.T) { - subscriptionId := "12345678-1234-5678-1234-123456789012" - id := NewExpressRouteGatewayID("resourceGroup1", "expressRouteGateway1") - actual := id.ID(subscriptionId) - expected := "/subscriptions/12345678-1234-5678-1234-123456789012/resourceGroups/resourceGroup1/providers/Microsoft.Network/expressRouteGateways/expressRouteGateway1" - + actual := NewExpressRouteGatewayID("12345678-1234-9876-4563-123456789012", "resGroup1", "ergw1").ID() + expected := "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteGateways/ergw1" if actual != expected { t.Fatalf("Expected %q but got %q", expected, actual) } @@ -20,65 +20,91 @@ func TestExpressRouteGatewayIDFormatter(t *testing.T) { func TestExpressRouteGatewayID(t *testing.T) { testData := []struct { - Name string Input string + Error bool Expected *ExpressRouteGatewayId }{ + + { + // empty + Input: "", + Error: true, + }, + + { + // missing SubscriptionId + Input: "/", + Error: true, + }, + { - Name: "Empty", - Input: "", - Expected: nil, + // missing value for SubscriptionId + Input: "/subscriptions/", + Error: true, }, + { - Name: "No Resource Groups Segment", - Input: "/subscriptions/00000000-0000-0000-0000-000000000000", - Expected: nil, + // missing ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", + Error: true, }, + { - Name: "No Resource Groups Value", - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/", - Expected: nil, + // missing value for ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", + Error: true, }, + { - Name: "Resource Group ID", - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/", - Expected: nil, + // missing Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/", + Error: true, }, + { - Name: "Missing ExpressRouteGateway Value", - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup1/providers/Microsoft.Network/expressRouteGateways", - Expected: nil, + // missing value for Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteGateways/", + Error: true, }, + { - Name: "network ExpressRouteGateway ID", - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup1/providers/Microsoft.Network/expressRouteGateways/expressRouteGateway1", + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteGateways/ergw1", Expected: &ExpressRouteGatewayId{ - ResourceGroup: "resourceGroup1", - Name: "expressRouteGateway1", + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resGroup1", + Name: "ergw1", }, }, + { - Name: "Wrong Casing", - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup1/providers/Microsoft.Network/ExpressRouteGateways/expressRouteGateway1", - Expected: nil, + // upper-cased + Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.NETWORK/EXPRESSROUTEGATEWAYS/ERGW1", + Error: true, }, } for _, v := range testData { - t.Logf("[DEBUG] Testing %q..", v.Name) + t.Logf("[DEBUG] Testing %q", v.Input) actual, err := ExpressRouteGatewayID(v.Input) if err != nil { - if v.Expected == nil { + if v.Error { continue } - t.Fatalf("Expected a value but got an error: %s", err) + + t.Fatalf("Expect a value but got an error: %s", err) + } + if v.Error { + t.Fatal("Expect an error but didn't get one") } + if actual.SubscriptionId != v.Expected.SubscriptionId { + t.Fatalf("Expected %q but got %q for SubscriptionId", v.Expected.SubscriptionId, actual.SubscriptionId) + } if actual.ResourceGroup != v.Expected.ResourceGroup { t.Fatalf("Expected %q but got %q for ResourceGroup", v.Expected.ResourceGroup, actual.ResourceGroup) } - if actual.Name != v.Expected.Name { t.Fatalf("Expected %q but got %q for Name", v.Expected.Name, actual.Name) } diff --git a/azurerm/internal/services/network/resourceids.go b/azurerm/internal/services/network/resourceids.go index e5294b4e229a..bb3d14b0b491 100644 --- a/azurerm/internal/services/network/resourceids.go +++ b/azurerm/internal/services/network/resourceids.go @@ -56,3 +56,5 @@ package network // Express Route Connection //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=ExpressRouteConnection -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteGateways/ergw1/expressRouteConnections/erConnection1 +//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=ExpressRouteCircuitPeering -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteCircuits/erCircuit1/peerings/peering1 +//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=ExpressRouteGateway -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteGateways/ergw1 diff --git a/azurerm/internal/services/network/validate/express_route_circuit_peering.go b/azurerm/internal/services/network/validate/express_route_circuit_peering.go deleted file mode 100644 index 0c2b30f0eaa7..000000000000 --- a/azurerm/internal/services/network/validate/express_route_circuit_peering.go +++ /dev/null @@ -1,20 +0,0 @@ -package validate - -import ( - "fmt" - - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/parse" -) - -func ExpressRouteCircuitPeeringID(i interface{}, k string) (warnings []string, errors []error) { - v, ok := i.(string) - if !ok { - return nil, []error{fmt.Errorf("expected type of %q to be string", k)} - } - - if _, err := parse.ExpressRouteCircuitPeeringID(v); err != nil { - return nil, []error{err} - } - - return nil, nil -} diff --git a/azurerm/internal/services/network/validate/express_route_circuit_peering_id.go b/azurerm/internal/services/network/validate/express_route_circuit_peering_id.go new file mode 100644 index 000000000000..15a68871c874 --- /dev/null +++ b/azurerm/internal/services/network/validate/express_route_circuit_peering_id.go @@ -0,0 +1,23 @@ +package validate + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "fmt" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/parse" +) + +func ExpressRouteCircuitPeeringID(input interface{}, key string) (warnings []string, errors []error) { + v, ok := input.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected %q to be a string", key)) + return + } + + if _, err := parse.ExpressRouteCircuitPeeringID(v); err != nil { + errors = append(errors, err) + } + + return +} diff --git a/azurerm/internal/services/network/validate/express_route_circuit_peering_id_test.go b/azurerm/internal/services/network/validate/express_route_circuit_peering_id_test.go new file mode 100644 index 000000000000..cb7df71488d0 --- /dev/null +++ b/azurerm/internal/services/network/validate/express_route_circuit_peering_id_test.go @@ -0,0 +1,88 @@ +package validate + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import "testing" + +func TestExpressRouteCircuitPeeringID(t *testing.T) { + cases := []struct { + Input string + Valid bool + }{ + + { + // empty + Input: "", + Valid: false, + }, + + { + // missing SubscriptionId + Input: "/", + Valid: false, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Valid: false, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", + Valid: false, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", + Valid: false, + }, + + { + // missing ExpressRouteCircuitName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/", + Valid: false, + }, + + { + // missing value for ExpressRouteCircuitName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteCircuits/", + Valid: false, + }, + + { + // missing PeeringName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteCircuits/erCircuit1/", + Valid: false, + }, + + { + // missing value for PeeringName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteCircuits/erCircuit1/peerings/", + Valid: false, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteCircuits/erCircuit1/peerings/peering1", + Valid: true, + }, + + { + // upper-cased + Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.NETWORK/EXPRESSROUTECIRCUITS/ERCIRCUIT1/PEERINGS/PEERING1", + Valid: false, + }, + } + for _, tc := range cases { + t.Logf("[DEBUG] Testing Value %s", tc.Input) + _, errors := ExpressRouteCircuitPeeringID(tc.Input, "test") + valid := len(errors) == 0 + + if tc.Valid != valid { + t.Fatalf("Expected %t but got %t", tc.Valid, valid) + } + } +} diff --git a/azurerm/internal/services/network/validate/express_route_circuit_peering_test.go b/azurerm/internal/services/network/validate/express_route_circuit_peering_test.go deleted file mode 100644 index a13a95a54333..000000000000 --- a/azurerm/internal/services/network/validate/express_route_circuit_peering_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package validate - -import "testing" - -func TestExpressRouteCircuitPeeringID(t *testing.T) { - testData := []struct { - Name string - Input string - Valid bool - }{ - { - Name: "Empty", - Input: "", - Valid: false, - }, - { - Name: "No expressRouteCircuits Segment", - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo", - Valid: false, - }, - { - Name: "No expressRouteCircuits Value", - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/expressRouteCircuits/", - Valid: false, - }, - { - Name: "No peerings Segment", - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/expressRouteCircuits/circuit1", - Valid: false, - }, - { - Name: "No peerings Value", - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/expressRouteCircuits/circuit1/peerings/", - Valid: false, - }, - { - Name: "Completed", - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/expressRouteCircuits/circuit1/peerings/peering1", - Valid: true, - }, - } - - for _, v := range testData { - t.Logf("[DEBUG] Testing %q", v.Input) - - _, errors := ExpressRouteCircuitPeeringID(v.Input, "express_route_circuit_peering_id") - isValid := len(errors) == 0 - if v.Valid != isValid { - t.Fatalf("Expected %t but got %t", v.Valid, isValid) - } - } -} diff --git a/azurerm/internal/services/network/validate/express_route_gateway.go b/azurerm/internal/services/network/validate/express_route_gateway.go deleted file mode 100644 index e0f63847fd95..000000000000 --- a/azurerm/internal/services/network/validate/express_route_gateway.go +++ /dev/null @@ -1,20 +0,0 @@ -package validate - -import ( - "fmt" - - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/parse" -) - -func ExpressRouteGatewayID(i interface{}, k string) (warnings []string, errors []error) { - v, ok := i.(string) - if !ok { - return nil, []error{fmt.Errorf("expected type of %q to be string", k)} - } - - if _, err := parse.ExpressRouteGatewayID(v); err != nil { - return nil, []error{err} - } - - return nil, nil -} diff --git a/azurerm/internal/services/network/validate/express_route_gateway_id.go b/azurerm/internal/services/network/validate/express_route_gateway_id.go new file mode 100644 index 000000000000..51d29b695994 --- /dev/null +++ b/azurerm/internal/services/network/validate/express_route_gateway_id.go @@ -0,0 +1,23 @@ +package validate + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "fmt" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/parse" +) + +func ExpressRouteGatewayID(input interface{}, key string) (warnings []string, errors []error) { + v, ok := input.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected %q to be a string", key)) + return + } + + if _, err := parse.ExpressRouteGatewayID(v); err != nil { + errors = append(errors, err) + } + + return +} diff --git a/azurerm/internal/services/network/validate/express_route_gateway_id_test.go b/azurerm/internal/services/network/validate/express_route_gateway_id_test.go new file mode 100644 index 000000000000..01c18098ea4f --- /dev/null +++ b/azurerm/internal/services/network/validate/express_route_gateway_id_test.go @@ -0,0 +1,76 @@ +package validate + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import "testing" + +func TestExpressRouteGatewayID(t *testing.T) { + cases := []struct { + Input string + Valid bool + }{ + + { + // empty + Input: "", + Valid: false, + }, + + { + // missing SubscriptionId + Input: "/", + Valid: false, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Valid: false, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", + Valid: false, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", + Valid: false, + }, + + { + // missing Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/", + Valid: false, + }, + + { + // missing value for Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteGateways/", + Valid: false, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteGateways/ergw1", + Valid: true, + }, + + { + // upper-cased + Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.NETWORK/EXPRESSROUTEGATEWAYS/ERGW1", + Valid: false, + }, + } + for _, tc := range cases { + t.Logf("[DEBUG] Testing Value %s", tc.Input) + _, errors := ExpressRouteGatewayID(tc.Input, "test") + valid := len(errors) == 0 + + if tc.Valid != valid { + t.Fatalf("Expected %t but got %t", tc.Valid, valid) + } + } +} diff --git a/azurerm/internal/services/network/validate/express_route_gateway_test.go b/azurerm/internal/services/network/validate/express_route_gateway_test.go deleted file mode 100644 index d3ef3b9a5fae..000000000000 --- a/azurerm/internal/services/network/validate/express_route_gateway_test.go +++ /dev/null @@ -1,42 +0,0 @@ -package validate - -import "testing" - -func TestExpressRouteGatewayID(t *testing.T) { - testData := []struct { - Name string - Input string - Valid bool - }{ - { - Name: "Empty", - Input: "", - Valid: false, - }, - { - Name: "No expressRouteGateways Segment", - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo", - Valid: false, - }, - { - Name: "No expressRouteGateways Value", - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/expressRouteGateways/", - Valid: false, - }, - { - Name: "Completed", - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/expressRouteGateways/expressRouteGateway1", - Valid: true, - }, - } - - for _, v := range testData { - t.Logf("[DEBUG] Testing %q", v.Input) - - _, errors := ExpressRouteGatewayID(v.Input, "express_route_gateway_id") - isValid := len(errors) == 0 - if v.Valid != isValid { - t.Fatalf("Expected %t but got %t", v.Valid, isValid) - } - } -} From 32d591e41bdf30232964afca4e1ce9f64996a651 Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Fri, 12 Mar 2021 20:59:41 +0800 Subject: [PATCH 04/32] update code --- .../express_route_circuit_peering_resource.go | 22 ++++---- .../express_route_connection_resource.go | 2 +- .../network/express_route_gateway_resource.go | 51 +++++++++---------- .../internal/services/network/resourceids.go | 2 +- 4 files changed, 37 insertions(+), 40 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 f898d55859aa..c275aea69c06 100644 --- a/azurerm/internal/services/network/express_route_circuit_peering_resource.go +++ b/azurerm/internal/services/network/express_route_circuit_peering_resource.go @@ -15,6 +15,7 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/locks" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/parse" + azSchema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/schema" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -26,9 +27,10 @@ func resourceExpressRouteCircuitPeering() *schema.Resource { Update: resourceExpressRouteCircuitPeeringCreateUpdate, Delete: resourceExpressRouteCircuitPeeringDelete, - Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, - }, + Importer: azSchema.ValidateResourceIDPriorToImport(func(id string) error { + _, err := parse.ExpressRouteCircuitPeeringID(id) + return err + }), Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(30 * time.Minute), @@ -193,6 +195,7 @@ func resourceExpressRouteCircuitPeering() *schema.Resource { } func resourceExpressRouteCircuitPeeringCreateUpdate(d *schema.ResourceData, meta interface{}) error { + subscriptionId := meta.(*clients.Client).Account.SubscriptionId client := meta.(*clients.Client).Network.ExpressRoutePeeringsClient ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) defer cancel() @@ -206,6 +209,8 @@ func resourceExpressRouteCircuitPeeringCreateUpdate(d *schema.ResourceData, meta locks.ByName(circuitName, expressRouteCircuitResourceName) defer locks.UnlockByName(circuitName, expressRouteCircuitResourceName) + id := parse.NewExpressRouteCircuitPeeringID(subscriptionId, resourceGroup, circuitName, peeringType) + if d.IsNewResource() { existing, err := client.Get(ctx, resourceGroup, circuitName, peeringType) if err != nil { @@ -214,8 +219,8 @@ func resourceExpressRouteCircuitPeeringCreateUpdate(d *schema.ResourceData, meta } } - if existing.ID != nil && *existing.ID != "" { - return tf.ImportAsExistsError("azurerm_express_route_circuit_peering", *existing.ID) + if !utils.ResponseWasNotFound(existing.Response) { + return tf.ImportAsExistsError("azurerm_express_route_circuit_peering", id.ID()) } } @@ -280,12 +285,7 @@ func resourceExpressRouteCircuitPeeringCreateUpdate(d *schema.ResourceData, meta return err } - read, err := client.Get(ctx, resourceGroup, circuitName, peeringType) - if err != nil { - return err - } - - d.SetId(*read.ID) + d.SetId(id.ID()) return resourceExpressRouteCircuitPeeringRead(d, meta) } diff --git a/azurerm/internal/services/network/express_route_connection_resource.go b/azurerm/internal/services/network/express_route_connection_resource.go index 1d8ff609eb9f..422c5c7770b8 100644 --- a/azurerm/internal/services/network/express_route_connection_resource.go +++ b/azurerm/internal/services/network/express_route_connection_resource.go @@ -174,7 +174,7 @@ func resourceArmExpressRouteConnectionCreateUpdate(d *schema.ResourceData, meta } } - if existing.ID != nil && *existing.ID != "" { + if !utils.ResponseWasNotFound(existing.Response) { return tf.ImportAsExistsError("azurerm_express_route_connection", id.ID()) } } diff --git a/azurerm/internal/services/network/express_route_gateway_resource.go b/azurerm/internal/services/network/express_route_gateway_resource.go index e1b65a65aa71..117aca4f84b4 100644 --- a/azurerm/internal/services/network/express_route_gateway_resource.go +++ b/azurerm/internal/services/network/express_route_gateway_resource.go @@ -12,7 +12,9 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/parse" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags" + azSchema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/schema" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -23,9 +25,11 @@ func resourceExpressRouteGateway() *schema.Resource { Read: resourceExpressRouteGatewayRead, Update: resourceExpressRouteGatewayCreateUpdate, Delete: resourceExpressRouteGatewayDelete, - Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, - }, + + Importer: azSchema.ValidateResourceIDPriorToImport(func(id string) error { + _, err := parse.ExpressRouteGatewayID(id) + return err + }), Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(90 * time.Minute), @@ -65,6 +69,7 @@ func resourceExpressRouteGateway() *schema.Resource { } func resourceExpressRouteGatewayCreateUpdate(d *schema.ResourceData, meta interface{}) error { + subscriptionId := meta.(*clients.Client).Account.SubscriptionId client := meta.(*clients.Client).Network.ExpressRouteGatewaysClient ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) defer cancel() @@ -74,15 +79,18 @@ func resourceExpressRouteGatewayCreateUpdate(d *schema.ResourceData, meta interf name := d.Get("name").(string) resourceGroup := d.Get("resource_group_name").(string) + id := parse.NewExpressRouteGatewayID(subscriptionId, resourceGroup, name) + if d.IsNewResource() { - resp, err := client.Get(ctx, resourceGroup, name) + existing, err := client.Get(ctx, resourceGroup, name) if err != nil { - if !utils.ResponseWasNotFound(resp.Response) { + if !utils.ResponseWasNotFound(existing.Response) { return fmt.Errorf("Error checking for present of existing ExpressRoute Gateway %q (Resource Group %q): %+v", name, resourceGroup, err) } } - if resp.ID != nil && *resp.ID != "" { - return tf.ImportAsExistsError("azurerm_express_route_gateway", *resp.ID) + + if !utils.ResponseWasNotFound(existing.Response) { + return tf.ImportAsExistsError("azurerm_express_route_gateway", id.ID()) } } @@ -114,14 +122,7 @@ func resourceExpressRouteGatewayCreateUpdate(d *schema.ResourceData, meta interf return fmt.Errorf("Error waiting for creation of ExpressRoute Gateway %q (Resource Group %q): %+v", name, resourceGroup, err) } - resp, err := client.Get(ctx, resourceGroup, name) - if err != nil { - return fmt.Errorf("Error retrieving ExpressRoute Gateway %q (Resource Group %q): %+v", name, resourceGroup, err) - } - if resp.ID == nil || *resp.ID == "" { - return fmt.Errorf("Cannot read ExpressRoute Gateway %q (Resource Group %q) ID", name, resourceGroup) - } - d.SetId(*resp.ID) + d.SetId(id.ID()) return resourceExpressRouteGatewayRead(d, meta) } @@ -131,25 +132,23 @@ func resourceExpressRouteGatewayRead(d *schema.ResourceData, meta interface{}) e ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) defer cancel() - id, err := azure.ParseAzureResourceID(d.Id()) + id, err := parse.ExpressRouteGatewayID(d.Id()) if err != nil { return err } - resourceGroup := id.ResourceGroup - name := id.Path["expressRouteGateways"] - resp, err := client.Get(ctx, resourceGroup, name) + resp, err := client.Get(ctx, id.ResourceGroup, id.Name) if err != nil { if utils.ResponseWasNotFound(resp.Response) { log.Printf("[INFO] ExpressRoute Gateway %q does not exist - removing from state", d.Id()) d.SetId("") return nil } - return fmt.Errorf("Error reading ExpressRoute Gateway %q (Resource Group %q): %+v", name, resourceGroup, err) + return fmt.Errorf("Error reading ExpressRoute Gateway %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) } d.Set("name", resp.Name) - d.Set("resource_group_name", resourceGroup) + d.Set("resource_group_name", id.ResourceGroup) if location := resp.Location; location != nil { d.Set("location", azure.NormalizeLocation(*location)) } @@ -176,24 +175,22 @@ func resourceExpressRouteGatewayDelete(d *schema.ResourceData, meta interface{}) ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) defer cancel() - id, err := azure.ParseAzureResourceID(d.Id()) + id, err := parse.ExpressRouteGatewayID(d.Id()) if err != nil { return err } - resourceGroup := id.ResourceGroup - name := id.Path["expressRouteGateways"] - future, err := client.Delete(ctx, resourceGroup, name) + future, err := client.Delete(ctx, id.ResourceGroup, id.Name) if err != nil { if response.WasNotFound(future.Response()) { return nil } - return fmt.Errorf("Error deleting ExpressRoute Gateway %q (Resource Group %q): %+v", name, resourceGroup, err) + return fmt.Errorf("Error deleting ExpressRoute Gateway %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) } if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { if !response.WasNotFound(future.Response()) { - return fmt.Errorf("Error waiting for deleting ExpressRoute Gateway %q (Resource Group %q): %+v", name, resourceGroup, err) + return fmt.Errorf("Error waiting for deleting ExpressRoute Gateway %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) } } diff --git a/azurerm/internal/services/network/resourceids.go b/azurerm/internal/services/network/resourceids.go index bb3d14b0b491..3f4dde7ae5eb 100644 --- a/azurerm/internal/services/network/resourceids.go +++ b/azurerm/internal/services/network/resourceids.go @@ -55,6 +55,6 @@ package network //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=VirtualNetworkGatewayIpConfiguration -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/virtualNetworkGateways/gw1/ipConfigurations/cfg1 // Express Route Connection -//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=ExpressRouteConnection -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteGateways/ergw1/expressRouteConnections/erConnection1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=ExpressRouteCircuitPeering -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteCircuits/erCircuit1/peerings/peering1 +//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=ExpressRouteConnection -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteGateways/ergw1/expressRouteConnections/erConnection1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=ExpressRouteGateway -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteGateways/ergw1 From 1b865707589638722a8d2b12b2f81eb2573504ae Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Fri, 12 Mar 2021 22:12:01 +0800 Subject: [PATCH 05/32] update code --- .../express_route_connection_resource.go | 2 +- .../validate/express_route_connection_name.go | 16 ++++++ .../express_route_connection_name_test.go | 57 +++++++++++++++++++ .../r/express_route_connection.html.markdown | 2 + 4 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 azurerm/internal/services/network/validate/express_route_connection_name.go create mode 100644 azurerm/internal/services/network/validate/express_route_connection_name_test.go diff --git a/azurerm/internal/services/network/express_route_connection_resource.go b/azurerm/internal/services/network/express_route_connection_resource.go index 422c5c7770b8..512fe2fb9ffa 100644 --- a/azurerm/internal/services/network/express_route_connection_resource.go +++ b/azurerm/internal/services/network/express_route_connection_resource.go @@ -40,7 +40,7 @@ func resourceArmExpressRouteConnection() *schema.Resource { Type: schema.TypeString, Required: true, ForceNew: true, - ValidateFunc: validation.StringIsNotEmpty, + ValidateFunc: validate.ExpressRouteConnectionName, }, "express_route_circuit_peering_id": { diff --git a/azurerm/internal/services/network/validate/express_route_connection_name.go b/azurerm/internal/services/network/validate/express_route_connection_name.go new file mode 100644 index 000000000000..b3e82cfb349c --- /dev/null +++ b/azurerm/internal/services/network/validate/express_route_connection_name.go @@ -0,0 +1,16 @@ +package validate + +import ( + "fmt" + "regexp" +) + +func ExpressRouteConnectionName(v interface{}, k string) (warnings []string, errors []error) { + value := v.(string) + + if matched := regexp.MustCompile(`^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9_.-]{0,78}[a-zA-Z0-9_])$`).Match([]byte(value)); !matched { + errors = append(errors, fmt.Errorf("%q must be between 1 and 80 characters in length, begin with a letter or number, end with a letter, number or underscore, and may contain only letters, numbers, underscores, periods or hyphens", k)) + } + + return warnings, errors +} diff --git a/azurerm/internal/services/network/validate/express_route_connection_name_test.go b/azurerm/internal/services/network/validate/express_route_connection_name_test.go new file mode 100644 index 000000000000..3cc1329154ac --- /dev/null +++ b/azurerm/internal/services/network/validate/express_route_connection_name_test.go @@ -0,0 +1,57 @@ +package validate + +import ( + "strings" + "testing" +) + +func TestExpressRouteConnectionName(t *testing.T) { + testCases := []struct { + Input string + Expected bool + }{ + { + Input: "", + Expected: false, + }, + { + Input: strings.Repeat("s", 79), + Expected: true, + }, + { + Input: strings.Repeat("s", 80), + Expected: true, + }, + { + Input: strings.Repeat("s", 81), + Expected: false, + }, + { + Input: "_", + Expected: false, + }, + { + Input: "a", + Expected: true, + }, + { + Input: "a_", + Expected: true, + }, + { + Input: "ab", + Expected: true, + }, + { + Input: "abc", + Expected: true, + }, + } + for _, v := range testCases { + _, errors := ExpressRouteConnectionName(v.Input, "name") + result := len(errors) == 0 + if result != v.Expected { + t.Fatalf("Expected the result to be %t but got %t (and %d errors)", v.Expected, result, len(errors)) + } + } +} diff --git a/website/docs/r/express_route_connection.html.markdown b/website/docs/r/express_route_connection.html.markdown index 3adc6f65e707..ec4e53522f40 100644 --- a/website/docs/r/express_route_connection.html.markdown +++ b/website/docs/r/express_route_connection.html.markdown @@ -10,6 +10,8 @@ description: |- Manages an Express Route Connection. +~> **NOTE:** The provider status of the Express Route Circuit must be provisioned while creating the Express Route Connection. + ## Example Usage ```hcl From c5f1d61f88d0e89144bbda4976f4cf8605e5c656 Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Sat, 13 Mar 2021 00:05:05 +0800 Subject: [PATCH 06/32] update code --- .../express_route_connection_resource_test.go | 253 ++++++++++++++++++ .../r/express_route_connection.html.markdown | 2 +- 2 files changed, 254 insertions(+), 1 deletion(-) create mode 100644 azurerm/internal/services/network/express_route_connection_resource_test.go diff --git a/azurerm/internal/services/network/express_route_connection_resource_test.go b/azurerm/internal/services/network/express_route_connection_resource_test.go new file mode 100644 index 000000000000..2f2c336a70df --- /dev/null +++ b/azurerm/internal/services/network/express_route_connection_resource_test.go @@ -0,0 +1,253 @@ +package network_test + +import ( + "context" + "fmt" + "os" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance/check" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/parse" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +type ExpressRouteConnectionResource struct{} + +func TestAccExpressRouteConnection_basic(t *testing.T) { + expressRouteCircuitRg, expressRouteCircuitName, err := testGetExpressRouteCircuitResourceGroupAndNameFromSubscription() + if err != nil { + t.Skip(fmt.Sprintf("%+v", err)) + return + } + + data := acceptance.BuildTestData(t, "azurerm_express_route_connection", "test") + r := ExpressRouteConnectionResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.basic(data, expressRouteCircuitRg, expressRouteCircuitName), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccExpressRouteConnection_requiresImport(t *testing.T) { + expressRouteCircuitRG, expressRouteCircuitName, err := testGetExpressRouteCircuitResourceGroupAndNameFromSubscription() + if err != nil { + t.Skip(fmt.Sprintf("%+v", err)) + return + } + + data := acceptance.BuildTestData(t, "azurerm_express_route_connection", "test") + r := ExpressRouteConnectionResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.basic(data, expressRouteCircuitRG, expressRouteCircuitName), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.RequiresImportErrorStep(r.requiresImport), + }) +} + +func TestAccExpressRouteConnection_complete(t *testing.T) { + expressRouteCircuitRG, expressRouteCircuitName, err := testGetExpressRouteCircuitResourceGroupAndNameFromSubscription() + if err != nil { + t.Skip(fmt.Sprintf("%+v", err)) + return + } + + data := acceptance.BuildTestData(t, "azurerm_express_route_connection", "test") + r := ExpressRouteConnectionResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.complete(data, expressRouteCircuitRG, expressRouteCircuitName), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccExpressRouteConnection_update(t *testing.T) { + expressRouteCircuitRG, expressRouteCircuitName, err := testGetExpressRouteCircuitResourceGroupAndNameFromSubscription() + if err != nil { + t.Skip(fmt.Sprintf("%+v", err)) + return + } + + data := acceptance.BuildTestData(t, "azurerm_express_route_connection", "test") + r := ExpressRouteConnectionResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.basic(data, expressRouteCircuitRG, expressRouteCircuitName), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.complete(data, expressRouteCircuitRG, expressRouteCircuitName), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.basic(data, expressRouteCircuitRG, expressRouteCircuitName), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func (r ExpressRouteConnectionResource) Exists(ctx context.Context, client *clients.Client, state *terraform.InstanceState) (*bool, error) { + expressRouteConnectionClient := client.Network.ExpressRouteConnectionsClient + id, err := parse.ExpressRouteConnectionID(state.ID) + if err != nil { + return nil, err + } + + resp, err := expressRouteConnectionClient.Get(ctx, id.ResourceGroup, id.ExpressRouteGatewayName, id.Name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return utils.Bool(false), nil + } + + return nil, fmt.Errorf("retrieving Express Route Connection %q: %+v", state.ID, err) + } + + return utils.Bool(resp.ExpressRouteConnectionProperties != nil), nil +} + +func (r ExpressRouteConnectionResource) basic(data acceptance.TestData, expressRouteCircuitRG string, expressRouteCircuitName string) string { + return fmt.Sprintf(` +%s + +resource "azurerm_express_route_connection" "test" { + name = "acctest-ExpressRouteConnection-%d" + express_route_gateway_id = azurerm_express_route_gateway.test.id + express_route_circuit_peering_id = azurerm_express_route_circuit_peering.test.id +} +`, r.template(data, expressRouteCircuitRG, expressRouteCircuitName), data.RandomInteger) +} + +func (r ExpressRouteConnectionResource) requiresImport(data acceptance.TestData) string { + expressRouteCircuitRG, expressRouteCircuitName, err := testGetExpressRouteCircuitResourceGroupAndNameFromSubscription() + if err != nil { + return "" + } + + config := r.basic(data, expressRouteCircuitRG, expressRouteCircuitName) + return fmt.Sprintf(` +%s + +resource "azurerm_express_route_connection" "import" { + name = azurerm_express_route_connection.test.name + express_route_gateway_id = azurerm_express_route_connection.test.express_route_gateway_id + express_route_circuit_peering_id = azurerm_express_route_connection.test.express_route_circuit_peering_id +} +`, config) +} + +func (r ExpressRouteConnectionResource) complete(data acceptance.TestData, expressRouteCircuitRG string, expressRouteCircuitName string) string { + return fmt.Sprintf(` +%s + +resource "azurerm_express_route_connection" "test" { + name = "acctest-ExpressRouteConnection-%d" + express_route_gateway_id = azurerm_express_route_gateway.test.id + express_route_circuit_peering_id = azurerm_express_route_circuit_peering.test.id + routing_weight = 2 + authorization_key = "90f8db47-e25b-4b65-a68b-7743ced2a16b" + enable_internet_security = true +} +`, r.template(data, expressRouteCircuitRG, expressRouteCircuitName), data.RandomInteger) +} + +func (r ExpressRouteConnectionResource) template(data acceptance.TestData, expressRouteCircuitRG string, expressRouteCircuitName string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +data "azurerm_resource_group" "test" { + name = "%s" +} + +data "azurerm_express_route_circuit" "test" { + name = "%s" + resource_group_name = data.azurerm_resource_group.test.name +} + +resource "azurerm_resource_group" "test2" { + name = "acctestRG-erconnection-%d" + location = data.azurerm_resource_group.test.location +} + +resource "azurerm_express_route_circuit_peering" "test" { + peering_type = "AzurePrivatePeering" + express_route_circuit_name = data.azurerm_express_route_circuit.test.name + resource_group_name = data.azurerm_express_route_circuit.test.resource_group_name + shared_key = "ItsASecret" + peer_asn = 100 + primary_peer_address_prefix = "192.168.1.0/30" + secondary_peer_address_prefix = "192.168.2.0/30" + vlan_id = 100 +} + +resource "azurerm_virtual_wan" "test" { + name = "acctest-VWAN-%d" + resource_group_name = azurerm_resource_group.test2.name + location = azurerm_resource_group.test2.location +} + +resource "azurerm_virtual_hub" "test" { + name = "acctest-VHUB-%d" + resource_group_name = azurerm_resource_group.test2.name + location = azurerm_resource_group.test2.location + virtual_wan_id = azurerm_virtual_wan.test.id + address_prefix = "10.0.1.0/24" +} + +resource "azurerm_express_route_gateway" "test" { + name = "acctest-ERGW-%d" + resource_group_name = azurerm_resource_group.test2.name + location = azurerm_resource_group.test2.location + virtual_hub_id = azurerm_virtual_hub.test.id + scale_units = 1 +} +`, expressRouteCircuitRG, expressRouteCircuitName, data.RandomInteger, data.RandomInteger, data.RandomInteger, data.RandomInteger) +} + +func testGetExpressRouteCircuitResourceGroupAndNameFromSubscription() (string, string, error) { + subscription := strings.ToLower(os.Getenv("ARM_SUBSCRIPTION_ID")) + expressRouteCircuitRG := "" + expressRouteCircuitName := "" + + // As provider status of the Express Route Circuit must be set as provisioned manually by service team, so test cases have to use existing Express Route Circuit for testing. + // Once hashicorp gets the provisioned Express Route Circuit, the same logic for their subscriptions will also be added here. + if strings.HasPrefix(subscription, "67a9759d") || strings.HasPrefix(subscription, "85b3dbca") { + expressRouteCircuitRG = "xz3-test" + expressRouteCircuitName = "tf-er" + } else { + return expressRouteCircuitRG, expressRouteCircuitName, fmt.Errorf("Skipping since test is not running as one of the two valid subscriptions allowed to run ExpressRouteConnection tests") + } + + return expressRouteCircuitRG, expressRouteCircuitName, nil +} diff --git a/website/docs/r/express_route_connection.html.markdown b/website/docs/r/express_route_connection.html.markdown index ec4e53522f40..62262bc525df 100644 --- a/website/docs/r/express_route_connection.html.markdown +++ b/website/docs/r/express_route_connection.html.markdown @@ -10,7 +10,7 @@ description: |- Manages an Express Route Connection. -~> **NOTE:** The provider status of the Express Route Circuit must be provisioned while creating the Express Route Connection. +~> **NOTE:** The provider status of the Express Route Circuit must be set as provisioned while creating the Express Route Connection. ## Example Usage From 4778cf9bdc5be865748f1c4c718c94a8d5899ed5 Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Mon, 15 Mar 2021 14:45:32 +0800 Subject: [PATCH 07/32] update code --- .../express_route_connection_resource.go | 105 +----------------- .../express_route_connection_resource_test.go | 38 +++---- .../r/express_route_connection.html.markdown | 12 -- 3 files changed, 19 insertions(+), 136 deletions(-) diff --git a/azurerm/internal/services/network/express_route_connection_resource.go b/azurerm/internal/services/network/express_route_connection_resource.go index 512fe2fb9ffa..315fae114fa0 100644 --- a/azurerm/internal/services/network/express_route_connection_resource.go +++ b/azurerm/internal/services/network/express_route_connection_resource.go @@ -111,35 +111,6 @@ func resourceArmExpressRouteConnection() *schema.Resource { }, }, }, - - "static_vnet_route": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - - "address_prefixes": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validation.IsCIDR, - }, - }, - - "next_hop_ip_address": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.IsIPv4Address, - }, - }, - }, - }, }, }, }, @@ -158,7 +129,6 @@ func resourceArmExpressRouteConnectionCreateUpdate(d *schema.ResourceData, meta defer cancel() name := d.Get("name").(string) - resourceGroup := d.Get("resource_group_name").(string) expressRouteGatewayId, err := parse.ExpressRouteGatewayID(d.Get("express_route_gateway_id").(string)) if err != nil { return err @@ -167,7 +137,7 @@ func resourceArmExpressRouteConnectionCreateUpdate(d *schema.ResourceData, meta id := parse.NewExpressRouteConnectionID(expressRouteGatewayId.SubscriptionId, expressRouteGatewayId.ResourceGroup, expressRouteGatewayId.Name, name) if d.IsNewResource() { - existing, err := client.Get(ctx, resourceGroup, expressRouteCircuitResourceName, name) + existing, err := client.Get(ctx, id.ResourceGroup, id.ExpressRouteGatewayName, name) if err != nil { if !utils.ResponseWasNotFound(existing.Response) { return fmt.Errorf("checking for existing %s: %+v", id, err) @@ -204,7 +174,7 @@ func resourceArmExpressRouteConnectionCreateUpdate(d *schema.ResourceData, meta expressRouteConnectionParameters.ExpressRouteConnectionProperties.RoutingConfiguration = expandArmExpressRouteConnectionRouting(v.([]interface{})) } - future, err := client.CreateOrUpdate(ctx, resourceGroup, expressRouteGatewayId.Name, name, expressRouteConnectionParameters) + future, err := client.CreateOrUpdate(ctx, id.ResourceGroup, expressRouteGatewayId.Name, name, expressRouteConnectionParameters) if err != nil { return fmt.Errorf("creating/updating %s: %+v", id, err) } @@ -301,10 +271,6 @@ func expandArmExpressRouteConnectionRouting(input []interface{}) *network.Routin } } - if vnetStaticRoute := v["static_vnet_route"].([]interface{}); len(vnetStaticRoute) != 0 { - result.VnetRoutes = expandArmExpressRouteConnectionStaticVnetRoute(vnetStaticRoute) - } - if propagatedRouteTable := v["propagated_route_table"].([]interface{}); len(propagatedRouteTable) != 0 { result.PropagatedRouteTables = expandArmExpressRouteConnectionPropagatedRouteTable(propagatedRouteTable) } @@ -332,38 +298,6 @@ func expandArmExpressRouteConnectionPropagatedRouteTable(input []interface{}) *n return &result } -func expandArmExpressRouteConnectionStaticVnetRoute(input []interface{}) *network.VnetRoute { - if len(input) == 0 { - return &network.VnetRoute{} - } - - results := make([]network.StaticRoute, 0) - - for _, item := range input { - v := item.(map[string]interface{}) - - result := network.StaticRoute{} - - if name := v["name"].(string); name != "" { - result.Name = utils.String(name) - } - - if addressPrefixes := v["address_prefixes"].(*schema.Set).List(); len(addressPrefixes) != 0 { - result.AddressPrefixes = utils.ExpandStringSlice(addressPrefixes) - } - - if nextHopIPAddress := v["next_hop_ip_address"].(string); nextHopIPAddress != "" { - result.NextHopIPAddress = utils.String(nextHopIPAddress) - } - - results = append(results, result) - } - - return &network.VnetRoute{ - StaticRoutes: &results, - } -} - func flattenArmExpressRouteConnectionRouting(input *network.RoutingConfiguration) []interface{} { if input == nil { return []interface{}{} @@ -378,7 +312,6 @@ func flattenArmExpressRouteConnectionRouting(input *network.RoutingConfiguration map[string]interface{}{ "associated_route_table_id": associatedRouteTableId, "propagated_route_table": flattenArmExpressRouteConnectionPropagatedRouteTable(input.PropagatedRouteTables), - "static_vnet_route": flattenArmExpressRouteConnectionStaticVnetRoute(input.VnetRoutes), }, } } @@ -405,37 +338,3 @@ func flattenArmExpressRouteConnectionPropagatedRouteTable(input *network.Propaga }, } } - -func flattenArmExpressRouteConnectionStaticVnetRoute(input *network.VnetRoute) []interface{} { - results := make([]interface{}, 0) - if input == nil || input.StaticRoutes == nil { - return results - } - - for _, item := range *input.StaticRoutes { - var name string - if item.Name != nil { - name = *item.Name - } - - var nextHopIpAddress string - if item.NextHopIPAddress != nil { - nextHopIpAddress = *item.NextHopIPAddress - } - - addressPrefixes := make([]interface{}, 0) - if item.AddressPrefixes != nil { - addressPrefixes = utils.FlattenStringSlice(item.AddressPrefixes) - } - - v := map[string]interface{}{ - "name": name, - "address_prefixes": addressPrefixes, - "next_hop_ip_address": nextHopIpAddress, - } - - results = append(results, v) - } - - return results -} diff --git a/azurerm/internal/services/network/express_route_connection_resource_test.go b/azurerm/internal/services/network/express_route_connection_resource_test.go index 2f2c336a70df..71586edda1ce 100644 --- a/azurerm/internal/services/network/express_route_connection_resource_test.go +++ b/azurerm/internal/services/network/express_route_connection_resource_test.go @@ -106,13 +106,6 @@ func TestAccExpressRouteConnection_update(t *testing.T) { ), }, data.ImportStep(), - { - Config: r.basic(data, expressRouteCircuitRG, expressRouteCircuitName), - Check: resource.ComposeTestCheckFunc( - check.That(data.ResourceName).ExistsInAzure(r), - ), - }, - data.ImportStep(), }) } @@ -142,7 +135,7 @@ func (r ExpressRouteConnectionResource) basic(data acceptance.TestData, expressR resource "azurerm_express_route_connection" "test" { name = "acctest-ExpressRouteConnection-%d" express_route_gateway_id = azurerm_express_route_gateway.test.id - express_route_circuit_peering_id = azurerm_express_route_circuit_peering.test.id + express_route_circuit_peering_id = "${azurerm_express_route_circuit.test.id}/peerings/AzurePrivatePeering" } `, r.template(data, expressRouteCircuitRG, expressRouteCircuitName), data.RandomInteger) } @@ -169,15 +162,29 @@ func (r ExpressRouteConnectionResource) complete(data acceptance.TestData, expre return fmt.Sprintf(` %s +resource "azurerm_virtual_hub_route_table" "test" { + name = "acctest-VHUBRT-%d" + virtual_hub_id = azurerm_virtual_hub.test.id +} + resource "azurerm_express_route_connection" "test" { name = "acctest-ExpressRouteConnection-%d" express_route_gateway_id = azurerm_express_route_gateway.test.id - express_route_circuit_peering_id = azurerm_express_route_circuit_peering.test.id + express_route_circuit_peering_id = "${azurerm_express_route_circuit.test.id}/peerings/AzurePrivatePeering" routing_weight = 2 authorization_key = "90f8db47-e25b-4b65-a68b-7743ced2a16b" enable_internet_security = true + + routing { + associated_route_table_id = azurerm_virtual_hub_route_table.test.id + + propagated_route_table { + labels = ["label1"] + route_table_ids = [azurerm_virtual_hub_route_table.test.id] + } + } } -`, r.template(data, expressRouteCircuitRG, expressRouteCircuitName), data.RandomInteger) +`, r.template(data, expressRouteCircuitRG, expressRouteCircuitName), data.RandomInteger, data.RandomInteger) } func (r ExpressRouteConnectionResource) template(data acceptance.TestData, expressRouteCircuitRG string, expressRouteCircuitName string) string { @@ -200,17 +207,6 @@ resource "azurerm_resource_group" "test2" { location = data.azurerm_resource_group.test.location } -resource "azurerm_express_route_circuit_peering" "test" { - peering_type = "AzurePrivatePeering" - express_route_circuit_name = data.azurerm_express_route_circuit.test.name - resource_group_name = data.azurerm_express_route_circuit.test.resource_group_name - shared_key = "ItsASecret" - peer_asn = 100 - primary_peer_address_prefix = "192.168.1.0/30" - secondary_peer_address_prefix = "192.168.2.0/30" - vlan_id = 100 -} - resource "azurerm_virtual_wan" "test" { name = "acctest-VWAN-%d" resource_group_name = azurerm_resource_group.test2.name diff --git a/website/docs/r/express_route_connection.html.markdown b/website/docs/r/express_route_connection.html.markdown index 62262bc525df..4ad9a74d657c 100644 --- a/website/docs/r/express_route_connection.html.markdown +++ b/website/docs/r/express_route_connection.html.markdown @@ -102,8 +102,6 @@ A `routing` block supports the following: * `propagated_route_table` - (Optional) A `propagated_route_table` block as defined below. -* `static_vnet_route` - (Optional) A `static_vnet_route` block as defined below. - --- A `propagated_route_table` block supports the following: @@ -112,16 +110,6 @@ A `propagated_route_table` block supports the following: * `route_table_ids` - (Optional) A list of Route Table ID's to associated with this Virtual Hub Connection. ---- - -A `static_vnet_route` block supports the following: - -* `name` - (Optional) The name which should be used for this Static Route. - -* `address_prefixes` - (Optional) A list of CIDR Ranges which should be used as Address Prefixes. - -* `next_hop_ip_address` - (Optional) The IP Address which should be used for the Next Hop. - ## Attributes Reference The following attributes are exported: From f50cab1ed67e55d14c1051775591e942db081d77 Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Tue, 16 Mar 2021 14:34:24 +0800 Subject: [PATCH 08/32] update code --- .../express_route_circuit_peering_resource.go | 53 +++++++++++-------- .../express_route_connection_resource.go | 2 +- .../express_route_connection_resource_test.go | 33 ++++++++---- .../network/express_route_gateway_resource.go | 49 +++++++++-------- .../internal/services/network/resourceids.go | 2 +- .../r/express_route_connection.html.markdown | 2 +- 6 files changed, 83 insertions(+), 58 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 c275aea69c06..3128602d1ca9 100644 --- a/azurerm/internal/services/network/express_route_circuit_peering_resource.go +++ b/azurerm/internal/services/network/express_route_circuit_peering_resource.go @@ -15,7 +15,6 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/locks" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/parse" - azSchema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/schema" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -27,10 +26,9 @@ func resourceExpressRouteCircuitPeering() *schema.Resource { Update: resourceExpressRouteCircuitPeeringCreateUpdate, Delete: resourceExpressRouteCircuitPeeringDelete, - Importer: azSchema.ValidateResourceIDPriorToImport(func(id string) error { - _, err := parse.ExpressRouteCircuitPeeringID(id) - return err - }), + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(30 * time.Minute), @@ -195,7 +193,6 @@ func resourceExpressRouteCircuitPeering() *schema.Resource { } func resourceExpressRouteCircuitPeeringCreateUpdate(d *schema.ResourceData, meta interface{}) error { - subscriptionId := meta.(*clients.Client).Account.SubscriptionId client := meta.(*clients.Client).Network.ExpressRoutePeeringsClient ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) defer cancel() @@ -209,8 +206,6 @@ func resourceExpressRouteCircuitPeeringCreateUpdate(d *schema.ResourceData, meta locks.ByName(circuitName, expressRouteCircuitResourceName) defer locks.UnlockByName(circuitName, expressRouteCircuitResourceName) - id := parse.NewExpressRouteCircuitPeeringID(subscriptionId, resourceGroup, circuitName, peeringType) - if d.IsNewResource() { existing, err := client.Get(ctx, resourceGroup, circuitName, peeringType) if err != nil { @@ -219,8 +214,8 @@ func resourceExpressRouteCircuitPeeringCreateUpdate(d *schema.ResourceData, meta } } - if !utils.ResponseWasNotFound(existing.Response) { - return tf.ImportAsExistsError("azurerm_express_route_circuit_peering", id.ID()) + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("azurerm_express_route_circuit_peering", *existing.ID) } } @@ -285,7 +280,12 @@ func resourceExpressRouteCircuitPeeringCreateUpdate(d *schema.ResourceData, meta return err } - d.SetId(id.ID()) + read, err := client.Get(ctx, resourceGroup, circuitName, peeringType) + if err != nil { + return err + } + + d.SetId(*read.ID) return resourceExpressRouteCircuitPeeringRead(d, meta) } @@ -295,23 +295,26 @@ func resourceExpressRouteCircuitPeeringRead(d *schema.ResourceData, meta interfa ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) defer cancel() - id, err := parse.ExpressRouteCircuitPeeringID(d.Id()) + id, err := azure.ParseAzureResourceID(d.Id()) if err != nil { return err } + resourceGroup := id.ResourceGroup + circuitName := id.Path["expressRouteCircuits"] + peeringType := id.Path["peerings"] - resp, err := client.Get(ctx, id.ResourceGroup, id.ExpressRouteCircuitName, id.PeeringName) + resp, err := client.Get(ctx, resourceGroup, circuitName, peeringType) if err != nil { if utils.ResponseWasNotFound(resp.Response) { d.SetId("") return nil } - return fmt.Errorf("Error making Read request on Express Route Circuit Peering %q (Circuit %q / Resource Group %q): %+v", id.PeeringName, id.ExpressRouteCircuitName, id.ResourceGroup, err) + return fmt.Errorf("Error making Read request on Express Route Circuit Peering %q (Circuit %q / Resource Group %q): %+v", peeringType, circuitName, resourceGroup, err) } - d.Set("peering_type", id.PeeringName) - d.Set("express_route_circuit_name", id.ExpressRouteCircuitName) - d.Set("resource_group_name", id.ResourceGroup) + d.Set("peering_type", peeringType) + d.Set("express_route_circuit_name", circuitName) + d.Set("resource_group_name", resourceGroup) if props := resp.ExpressRouteCircuitPeeringPropertiesFormat; props != nil { d.Set("azure_asn", props.AzureASN) @@ -345,27 +348,31 @@ func resourceExpressRouteCircuitPeeringDelete(d *schema.ResourceData, meta inter ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) defer cancel() - id, err := parse.ExpressRouteCircuitPeeringID(d.Id()) + id, err := azure.ParseAzureResourceID(d.Id()) if err != nil { return err } - locks.ByName(id.ExpressRouteCircuitName, expressRouteCircuitResourceName) - defer locks.UnlockByName(id.ExpressRouteCircuitName, expressRouteCircuitResourceName) + resourceGroup := id.ResourceGroup + circuitName := id.Path["expressRouteCircuits"] + peeringType := id.Path["peerings"] + + locks.ByName(circuitName, expressRouteCircuitResourceName) + defer locks.UnlockByName(circuitName, expressRouteCircuitResourceName) - future, err := client.Delete(ctx, id.ResourceGroup, id.ExpressRouteCircuitName, id.PeeringName) + future, err := client.Delete(ctx, resourceGroup, circuitName, peeringType) if err != nil { if response.WasNotFound(future.Response()) { return nil } - return fmt.Errorf("Error issuing delete request for Express Route Circuit Peering %q (Circuit %q / Resource Group %q): %+v", id.PeeringName, id.ExpressRouteCircuitName, id.ResourceGroup, err) + return fmt.Errorf("Error issuing delete request for Express Route Circuit Peering %q (Circuit %q / Resource Group %q): %+v", peeringType, circuitName, resourceGroup, err) } if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { if response.WasNotFound(future.Response()) { return nil } - return fmt.Errorf("Error waiting for Express Route Circuit Peering %q (Circuit %q / Resource Group %q) to be deleted: %+v", id.PeeringName, id.ExpressRouteCircuitName, id.ResourceGroup, err) + return fmt.Errorf("Error waiting for Express Route Circuit Peering %q (Circuit %q / Resource Group %q) to be deleted: %+v", peeringType, circuitName, resourceGroup, err) } return err diff --git a/azurerm/internal/services/network/express_route_connection_resource.go b/azurerm/internal/services/network/express_route_connection_resource.go index 315fae114fa0..4c714e94c458 100644 --- a/azurerm/internal/services/network/express_route_connection_resource.go +++ b/azurerm/internal/services/network/express_route_connection_resource.go @@ -174,7 +174,7 @@ func resourceArmExpressRouteConnectionCreateUpdate(d *schema.ResourceData, meta expressRouteConnectionParameters.ExpressRouteConnectionProperties.RoutingConfiguration = expandArmExpressRouteConnectionRouting(v.([]interface{})) } - future, err := client.CreateOrUpdate(ctx, id.ResourceGroup, expressRouteGatewayId.Name, name, expressRouteConnectionParameters) + future, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.ExpressRouteGatewayName, name, expressRouteConnectionParameters) if err != nil { return fmt.Errorf("creating/updating %s: %+v", id, err) } diff --git a/azurerm/internal/services/network/express_route_connection_resource_test.go b/azurerm/internal/services/network/express_route_connection_resource_test.go index 71586edda1ce..78404ef517e8 100644 --- a/azurerm/internal/services/network/express_route_connection_resource_test.go +++ b/azurerm/internal/services/network/express_route_connection_resource_test.go @@ -18,7 +18,20 @@ import ( type ExpressRouteConnectionResource struct{} -func TestAccExpressRouteConnection_basic(t *testing.T) { +func TestAccExpressRouteConnection(t *testing.T) { + // NOTE: As the provider status of the Express Route Circuit only can be manually updated to `Provisioned` by service team, so currently there is only one authorized test data. + // And there can be only one Express Route Connection on the same Express Route Circuit, otherwise tests will conflict if run at the same time. + acceptance.RunTestsInSequence(t, map[string]map[string]func(t *testing.T){ + "Resource": { + "basic": testAccExpressRouteConnection_basic, + "requiresImport": testAccExpressRouteConnection_requiresImport, + "complete": testAccExpressRouteConnection_complete, + "update": testAccExpressRouteConnection_update, + }, + }) +} + +func testAccExpressRouteConnection_basic(t *testing.T) { expressRouteCircuitRg, expressRouteCircuitName, err := testGetExpressRouteCircuitResourceGroupAndNameFromSubscription() if err != nil { t.Skip(fmt.Sprintf("%+v", err)) @@ -28,7 +41,7 @@ func TestAccExpressRouteConnection_basic(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_express_route_connection", "test") r := ExpressRouteConnectionResource{} - data.ResourceTest(t, r, []resource.TestStep{ + data.ResourceSequentialTest(t, r, []resource.TestStep{ { Config: r.basic(data, expressRouteCircuitRg, expressRouteCircuitName), Check: resource.ComposeTestCheckFunc( @@ -39,7 +52,7 @@ func TestAccExpressRouteConnection_basic(t *testing.T) { }) } -func TestAccExpressRouteConnection_requiresImport(t *testing.T) { +func testAccExpressRouteConnection_requiresImport(t *testing.T) { expressRouteCircuitRG, expressRouteCircuitName, err := testGetExpressRouteCircuitResourceGroupAndNameFromSubscription() if err != nil { t.Skip(fmt.Sprintf("%+v", err)) @@ -49,7 +62,7 @@ func TestAccExpressRouteConnection_requiresImport(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_express_route_connection", "test") r := ExpressRouteConnectionResource{} - data.ResourceTest(t, r, []resource.TestStep{ + data.ResourceSequentialTest(t, r, []resource.TestStep{ { Config: r.basic(data, expressRouteCircuitRG, expressRouteCircuitName), Check: resource.ComposeTestCheckFunc( @@ -60,7 +73,7 @@ func TestAccExpressRouteConnection_requiresImport(t *testing.T) { }) } -func TestAccExpressRouteConnection_complete(t *testing.T) { +func testAccExpressRouteConnection_complete(t *testing.T) { expressRouteCircuitRG, expressRouteCircuitName, err := testGetExpressRouteCircuitResourceGroupAndNameFromSubscription() if err != nil { t.Skip(fmt.Sprintf("%+v", err)) @@ -70,7 +83,7 @@ func TestAccExpressRouteConnection_complete(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_express_route_connection", "test") r := ExpressRouteConnectionResource{} - data.ResourceTest(t, r, []resource.TestStep{ + data.ResourceSequentialTest(t, r, []resource.TestStep{ { Config: r.complete(data, expressRouteCircuitRG, expressRouteCircuitName), Check: resource.ComposeTestCheckFunc( @@ -81,7 +94,7 @@ func TestAccExpressRouteConnection_complete(t *testing.T) { }) } -func TestAccExpressRouteConnection_update(t *testing.T) { +func testAccExpressRouteConnection_update(t *testing.T) { expressRouteCircuitRG, expressRouteCircuitName, err := testGetExpressRouteCircuitResourceGroupAndNameFromSubscription() if err != nil { t.Skip(fmt.Sprintf("%+v", err)) @@ -91,7 +104,7 @@ func TestAccExpressRouteConnection_update(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_express_route_connection", "test") r := ExpressRouteConnectionResource{} - data.ResourceTest(t, r, []resource.TestStep{ + data.ResourceSequentialTest(t, r, []resource.TestStep{ { Config: r.basic(data, expressRouteCircuitRG, expressRouteCircuitName), Check: resource.ComposeTestCheckFunc( @@ -135,7 +148,7 @@ func (r ExpressRouteConnectionResource) basic(data acceptance.TestData, expressR resource "azurerm_express_route_connection" "test" { name = "acctest-ExpressRouteConnection-%d" express_route_gateway_id = azurerm_express_route_gateway.test.id - express_route_circuit_peering_id = "${azurerm_express_route_circuit.test.id}/peerings/AzurePrivatePeering" + express_route_circuit_peering_id = "${data.azurerm_express_route_circuit.test.id}/peerings/AzurePrivatePeering" } `, r.template(data, expressRouteCircuitRG, expressRouteCircuitName), data.RandomInteger) } @@ -170,7 +183,7 @@ resource "azurerm_virtual_hub_route_table" "test" { resource "azurerm_express_route_connection" "test" { name = "acctest-ExpressRouteConnection-%d" express_route_gateway_id = azurerm_express_route_gateway.test.id - express_route_circuit_peering_id = "${azurerm_express_route_circuit.test.id}/peerings/AzurePrivatePeering" + express_route_circuit_peering_id = "${data.azurerm_express_route_circuit.test.id}/peerings/AzurePrivatePeering" routing_weight = 2 authorization_key = "90f8db47-e25b-4b65-a68b-7743ced2a16b" enable_internet_security = true diff --git a/azurerm/internal/services/network/express_route_gateway_resource.go b/azurerm/internal/services/network/express_route_gateway_resource.go index 117aca4f84b4..6c2749a9d1ac 100644 --- a/azurerm/internal/services/network/express_route_gateway_resource.go +++ b/azurerm/internal/services/network/express_route_gateway_resource.go @@ -12,9 +12,7 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/parse" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags" - azSchema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/schema" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -26,10 +24,9 @@ func resourceExpressRouteGateway() *schema.Resource { Update: resourceExpressRouteGatewayCreateUpdate, Delete: resourceExpressRouteGatewayDelete, - Importer: azSchema.ValidateResourceIDPriorToImport(func(id string) error { - _, err := parse.ExpressRouteGatewayID(id) - return err - }), + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(90 * time.Minute), @@ -69,7 +66,6 @@ func resourceExpressRouteGateway() *schema.Resource { } func resourceExpressRouteGatewayCreateUpdate(d *schema.ResourceData, meta interface{}) error { - subscriptionId := meta.(*clients.Client).Account.SubscriptionId client := meta.(*clients.Client).Network.ExpressRouteGatewaysClient ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) defer cancel() @@ -79,18 +75,16 @@ func resourceExpressRouteGatewayCreateUpdate(d *schema.ResourceData, meta interf name := d.Get("name").(string) resourceGroup := d.Get("resource_group_name").(string) - id := parse.NewExpressRouteGatewayID(subscriptionId, resourceGroup, name) - if d.IsNewResource() { - existing, err := client.Get(ctx, resourceGroup, name) + resp, err := client.Get(ctx, resourceGroup, name) if err != nil { - if !utils.ResponseWasNotFound(existing.Response) { + if !utils.ResponseWasNotFound(resp.Response) { return fmt.Errorf("Error checking for present of existing ExpressRoute Gateway %q (Resource Group %q): %+v", name, resourceGroup, err) } } - if !utils.ResponseWasNotFound(existing.Response) { - return tf.ImportAsExistsError("azurerm_express_route_gateway", id.ID()) + if resp.ID != nil && *resp.ID != "" { + return tf.ImportAsExistsError("azurerm_express_route_gateway", *resp.ID) } } @@ -122,7 +116,14 @@ func resourceExpressRouteGatewayCreateUpdate(d *schema.ResourceData, meta interf return fmt.Errorf("Error waiting for creation of ExpressRoute Gateway %q (Resource Group %q): %+v", name, resourceGroup, err) } - d.SetId(id.ID()) + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + return fmt.Errorf("Error retrieving ExpressRoute Gateway %q (Resource Group %q): %+v", name, resourceGroup, err) + } + if resp.ID == nil || *resp.ID == "" { + return fmt.Errorf("Cannot read ExpressRoute Gateway %q (Resource Group %q) ID", name, resourceGroup) + } + d.SetId(*resp.ID) return resourceExpressRouteGatewayRead(d, meta) } @@ -132,23 +133,25 @@ func resourceExpressRouteGatewayRead(d *schema.ResourceData, meta interface{}) e ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) defer cancel() - id, err := parse.ExpressRouteGatewayID(d.Id()) + id, err := azure.ParseAzureResourceID(d.Id()) if err != nil { return err } + resourceGroup := id.ResourceGroup + name := id.Path["expressRouteGateways"] - resp, err := client.Get(ctx, id.ResourceGroup, id.Name) + resp, err := client.Get(ctx, resourceGroup, name) if err != nil { if utils.ResponseWasNotFound(resp.Response) { log.Printf("[INFO] ExpressRoute Gateway %q does not exist - removing from state", d.Id()) d.SetId("") return nil } - return fmt.Errorf("Error reading ExpressRoute Gateway %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) + return fmt.Errorf("Error reading ExpressRoute Gateway %q (Resource Group %q): %+v", name, resourceGroup, err) } d.Set("name", resp.Name) - d.Set("resource_group_name", id.ResourceGroup) + d.Set("resource_group_name", resourceGroup) if location := resp.Location; location != nil { d.Set("location", azure.NormalizeLocation(*location)) } @@ -175,22 +178,24 @@ func resourceExpressRouteGatewayDelete(d *schema.ResourceData, meta interface{}) ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) defer cancel() - id, err := parse.ExpressRouteGatewayID(d.Id()) + id, err := azure.ParseAzureResourceID(d.Id()) if err != nil { return err } + resourceGroup := id.ResourceGroup + name := id.Path["expressRouteGateways"] - future, err := client.Delete(ctx, id.ResourceGroup, id.Name) + future, err := client.Delete(ctx, resourceGroup, name) if err != nil { if response.WasNotFound(future.Response()) { return nil } - return fmt.Errorf("Error deleting ExpressRoute Gateway %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) + return fmt.Errorf("Error deleting ExpressRoute Gateway %q (Resource Group %q): %+v", name, resourceGroup, err) } if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { if !response.WasNotFound(future.Response()) { - return fmt.Errorf("Error waiting for deleting ExpressRoute Gateway %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) + return fmt.Errorf("Error waiting for deleting ExpressRoute Gateway %q (Resource Group %q): %+v", name, resourceGroup, err) } } diff --git a/azurerm/internal/services/network/resourceids.go b/azurerm/internal/services/network/resourceids.go index 3f4dde7ae5eb..44d176b18fc4 100644 --- a/azurerm/internal/services/network/resourceids.go +++ b/azurerm/internal/services/network/resourceids.go @@ -56,5 +56,5 @@ package network // Express Route Connection //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=ExpressRouteCircuitPeering -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteCircuits/erCircuit1/peerings/peering1 -//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=ExpressRouteConnection -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteGateways/ergw1/expressRouteConnections/erConnection1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=ExpressRouteGateway -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteGateways/ergw1 +//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=ExpressRouteConnection -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteGateways/ergw1/expressRouteConnections/erConnection1 diff --git a/website/docs/r/express_route_connection.html.markdown b/website/docs/r/express_route_connection.html.markdown index 4ad9a74d657c..0cbf97959b3b 100644 --- a/website/docs/r/express_route_connection.html.markdown +++ b/website/docs/r/express_route_connection.html.markdown @@ -10,7 +10,7 @@ description: |- Manages an Express Route Connection. -~> **NOTE:** The provider status of the Express Route Circuit must be set as provisioned while creating the Express Route Connection. +~> **NOTE:** The provider status of the Express Route Circuit must be set as provisioned while creating the Express Route Connection. See more details from https://docs.microsoft.com/en-us/azure/expressroute/expressroute-howto-circuit-portal-resource-manager#send-the-service-key-to-your-connectivity-provider-for-provisioning. ## Example Usage From ea3694c7d86a14b3e4c6097d8eaa68eec610c611 Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Tue, 16 Mar 2021 15:51:32 +0800 Subject: [PATCH 09/32] update code --- .../network/express_route_gateway_resource.go | 2 -- .../r/express_route_connection.html.markdown | 16 ++++++++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/azurerm/internal/services/network/express_route_gateway_resource.go b/azurerm/internal/services/network/express_route_gateway_resource.go index 6c2749a9d1ac..e1b65a65aa71 100644 --- a/azurerm/internal/services/network/express_route_gateway_resource.go +++ b/azurerm/internal/services/network/express_route_gateway_resource.go @@ -23,7 +23,6 @@ func resourceExpressRouteGateway() *schema.Resource { Read: resourceExpressRouteGatewayRead, Update: resourceExpressRouteGatewayCreateUpdate, Delete: resourceExpressRouteGatewayDelete, - Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, @@ -82,7 +81,6 @@ func resourceExpressRouteGatewayCreateUpdate(d *schema.ResourceData, meta interf return fmt.Errorf("Error checking for present of existing ExpressRoute Gateway %q (Resource Group %q): %+v", name, resourceGroup, err) } } - if resp.ID != nil && *resp.ID != "" { return tf.ImportAsExistsError("azurerm_express_route_gateway", *resp.ID) } diff --git a/website/docs/r/express_route_connection.html.markdown b/website/docs/r/express_route_connection.html.markdown index 0cbf97959b3b..7897774f8d80 100644 --- a/website/docs/r/express_route_connection.html.markdown +++ b/website/docs/r/express_route_connection.html.markdown @@ -82,23 +82,23 @@ The following arguments are supported: * `name` - (Required) The name which should be used for this Express Route Connection. Changing this forces a new resource to be created. -* `express_route_circuit_peering_id` - (Required) The ID of the Express Route Circuit Peering within which this connection should be created. Changing this forces a new resource to be created. +* `express_route_circuit_peering_id` - (Required) The ID of the Express Route Circuit Peering within this Express Route Connection should be created. Changing this forces a new resource to be created. -* `express_route_gateway_id` - (Required) The ID of the Express Route Gateway within which this connection should be created. Changing this forces a new resource to be created. +* `express_route_gateway_id` - (Required) The ID of the Express Route Gateway within this Express Route Connection should be created. Changing this forces a new resource to be created. -* `authorization_key` - (Optional) The authorization key to establish the connection. +* `authorization_key` - (Optional) The authorization key to establish the Express Route Connection. -* `enable_internet_security` - (Optional) Enable internet security. +* `enable_internet_security` - (Optional) Is internet security enabled for this Express Route Connection? * `routing` - (Optional) A `routing` block as defined below. -* `routing_weight` - (Optional) The routing weight associated to the connection. Must be between `0` and `32000`. +* `routing_weight` - (Optional) The routing weight associated to the Express Route Connection. Valid values are between `0` and `32000`. --- A `routing` block supports the following: -* `associated_route_table_id` - (Optional) The ID of the route table associated with this Virtual Hub connection. +* `associated_route_table_id` - (Optional) The ID of the Virtual Hub Route Table associated with this Virtual Hub Connection. * `propagated_route_table` - (Optional) A `propagated_route_table` block as defined below. @@ -106,9 +106,9 @@ A `routing` block supports the following: A `propagated_route_table` block supports the following: -* `labels` - (Optional) The list of labels to assign to this route table. +* `labels` - (Optional) The list of labels to assign to this Virtual Hub Route Table. -* `route_table_ids` - (Optional) A list of Route Table ID's to associated with this Virtual Hub Connection. +* `route_table_ids` - (Optional) A list of IDs of the Virtual Hub Route Table which are associated with the Virtual Hub Connection. ## Attributes Reference From 1d193cd79e6b4b71700c0eef09361fe77d92d4e4 Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Wed, 14 Apr 2021 09:38:08 +0800 Subject: [PATCH 10/32] update tc --- .../express_route_connection_resource_test.go | 109 ++++++------------ 1 file changed, 36 insertions(+), 73 deletions(-) diff --git a/azurerm/internal/services/network/express_route_connection_resource_test.go b/azurerm/internal/services/network/express_route_connection_resource_test.go index 78404ef517e8..37f8b2974063 100644 --- a/azurerm/internal/services/network/express_route_connection_resource_test.go +++ b/azurerm/internal/services/network/express_route_connection_resource_test.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "os" - "strings" "testing" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" @@ -19,31 +18,30 @@ import ( type ExpressRouteConnectionResource struct{} func TestAccExpressRouteConnection(t *testing.T) { + if os.Getenv("ARM_TEST_DATA_RESOURCE_GROUP") == "" || os.Getenv("ARM_TEST_CIRCUIT_NAME") == "" { + t.Skip("Skipping as ARM_TEST_DATA_RESOURCE_GROUP and/or ARM_TEST_CIRCUIT_NAME are not specified") + return + } + // NOTE: As the provider status of the Express Route Circuit only can be manually updated to `Provisioned` by service team, so currently there is only one authorized test data. // And there can be only one Express Route Connection on the same Express Route Circuit, otherwise tests will conflict if run at the same time. acceptance.RunTestsInSequence(t, map[string]map[string]func(t *testing.T){ "Resource": { - "basic": testAccExpressRouteConnection_basic, - "requiresImport": testAccExpressRouteConnection_requiresImport, - "complete": testAccExpressRouteConnection_complete, - "update": testAccExpressRouteConnection_update, + "basic": testAccExpressRouteConnection_basic, + //"requiresImport": testAccExpressRouteConnection_requiresImport, + //"complete": testAccExpressRouteConnection_complete, + //"update": testAccExpressRouteConnection_update, }, }) } func testAccExpressRouteConnection_basic(t *testing.T) { - expressRouteCircuitRg, expressRouteCircuitName, err := testGetExpressRouteCircuitResourceGroupAndNameFromSubscription() - if err != nil { - t.Skip(fmt.Sprintf("%+v", err)) - return - } - data := acceptance.BuildTestData(t, "azurerm_express_route_connection", "test") r := ExpressRouteConnectionResource{} data.ResourceSequentialTest(t, r, []resource.TestStep{ { - Config: r.basic(data, expressRouteCircuitRg, expressRouteCircuitName), + Config: r.basic(data), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -53,18 +51,12 @@ func testAccExpressRouteConnection_basic(t *testing.T) { } func testAccExpressRouteConnection_requiresImport(t *testing.T) { - expressRouteCircuitRG, expressRouteCircuitName, err := testGetExpressRouteCircuitResourceGroupAndNameFromSubscription() - if err != nil { - t.Skip(fmt.Sprintf("%+v", err)) - return - } - data := acceptance.BuildTestData(t, "azurerm_express_route_connection", "test") r := ExpressRouteConnectionResource{} data.ResourceSequentialTest(t, r, []resource.TestStep{ { - Config: r.basic(data, expressRouteCircuitRG, expressRouteCircuitName), + Config: r.basic(data), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -74,18 +66,12 @@ func testAccExpressRouteConnection_requiresImport(t *testing.T) { } func testAccExpressRouteConnection_complete(t *testing.T) { - expressRouteCircuitRG, expressRouteCircuitName, err := testGetExpressRouteCircuitResourceGroupAndNameFromSubscription() - if err != nil { - t.Skip(fmt.Sprintf("%+v", err)) - return - } - data := acceptance.BuildTestData(t, "azurerm_express_route_connection", "test") r := ExpressRouteConnectionResource{} data.ResourceSequentialTest(t, r, []resource.TestStep{ { - Config: r.complete(data, expressRouteCircuitRG, expressRouteCircuitName), + Config: r.complete(data), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -95,25 +81,19 @@ func testAccExpressRouteConnection_complete(t *testing.T) { } func testAccExpressRouteConnection_update(t *testing.T) { - expressRouteCircuitRG, expressRouteCircuitName, err := testGetExpressRouteCircuitResourceGroupAndNameFromSubscription() - if err != nil { - t.Skip(fmt.Sprintf("%+v", err)) - return - } - data := acceptance.BuildTestData(t, "azurerm_express_route_connection", "test") r := ExpressRouteConnectionResource{} data.ResourceSequentialTest(t, r, []resource.TestStep{ { - Config: r.basic(data, expressRouteCircuitRG, expressRouteCircuitName), + Config: r.basic(data), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), }, data.ImportStep(), { - Config: r.complete(data, expressRouteCircuitRG, expressRouteCircuitName), + Config: r.complete(data), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -141,25 +121,20 @@ func (r ExpressRouteConnectionResource) Exists(ctx context.Context, client *clie return utils.Bool(resp.ExpressRouteConnectionProperties != nil), nil } -func (r ExpressRouteConnectionResource) basic(data acceptance.TestData, expressRouteCircuitRG string, expressRouteCircuitName string) string { +func (r ExpressRouteConnectionResource) basic(data acceptance.TestData) string { return fmt.Sprintf(` %s resource "azurerm_express_route_connection" "test" { name = "acctest-ExpressRouteConnection-%d" express_route_gateway_id = azurerm_express_route_gateway.test.id - express_route_circuit_peering_id = "${data.azurerm_express_route_circuit.test.id}/peerings/AzurePrivatePeering" + express_route_circuit_peering_id = azurerm_express_route_circuit_peering.test.id } -`, r.template(data, expressRouteCircuitRG, expressRouteCircuitName), data.RandomInteger) +`, r.template(data), data.RandomInteger) } func (r ExpressRouteConnectionResource) requiresImport(data acceptance.TestData) string { - expressRouteCircuitRG, expressRouteCircuitName, err := testGetExpressRouteCircuitResourceGroupAndNameFromSubscription() - if err != nil { - return "" - } - - config := r.basic(data, expressRouteCircuitRG, expressRouteCircuitName) + config := r.basic(data) return fmt.Sprintf(` %s @@ -171,7 +146,7 @@ resource "azurerm_express_route_connection" "import" { `, config) } -func (r ExpressRouteConnectionResource) complete(data acceptance.TestData, expressRouteCircuitRG string, expressRouteCircuitName string) string { +func (r ExpressRouteConnectionResource) complete(data acceptance.TestData) string { return fmt.Sprintf(` %s @@ -183,7 +158,7 @@ resource "azurerm_virtual_hub_route_table" "test" { resource "azurerm_express_route_connection" "test" { name = "acctest-ExpressRouteConnection-%d" express_route_gateway_id = azurerm_express_route_gateway.test.id - express_route_circuit_peering_id = "${data.azurerm_express_route_circuit.test.id}/peerings/AzurePrivatePeering" + express_route_circuit_peering_id = azurerm_express_route_circuit_peering.test.id routing_weight = 2 authorization_key = "90f8db47-e25b-4b65-a68b-7743ced2a16b" enable_internet_security = true @@ -197,27 +172,32 @@ resource "azurerm_express_route_connection" "test" { } } } -`, r.template(data, expressRouteCircuitRG, expressRouteCircuitName), data.RandomInteger, data.RandomInteger) +`, r.template(data), data.RandomInteger, data.RandomInteger) } -func (r ExpressRouteConnectionResource) template(data acceptance.TestData, expressRouteCircuitRG string, expressRouteCircuitName string) string { +func (r ExpressRouteConnectionResource) template(data acceptance.TestData) string { + circuitRG := os.Getenv("ARM_TEST_DATA_RESOURCE_GROUP") + circuitName := os.Getenv("ARM_TEST_CIRCUIT_NAME") + return fmt.Sprintf(` provider "azurerm" { features {} } -data "azurerm_resource_group" "test" { - name = "%s" -} - -data "azurerm_express_route_circuit" "test" { - name = "%s" - resource_group_name = data.azurerm_resource_group.test.name +resource "azurerm_express_route_circuit_peering" "test" { + peering_type = "AzurePrivatePeering" + express_route_circuit_name = "%s" + resource_group_name = "%s" + shared_key = "ItsASecret" + peer_asn = 100 + primary_peer_address_prefix = "192.168.1.0/30" + secondary_peer_address_prefix = "192.168.2.0/30" + vlan_id = 100 } resource "azurerm_resource_group" "test2" { name = "acctestRG-erconnection-%d" - location = data.azurerm_resource_group.test.location + location = "%s" } resource "azurerm_virtual_wan" "test" { @@ -241,22 +221,5 @@ resource "azurerm_express_route_gateway" "test" { virtual_hub_id = azurerm_virtual_hub.test.id scale_units = 1 } -`, expressRouteCircuitRG, expressRouteCircuitName, data.RandomInteger, data.RandomInteger, data.RandomInteger, data.RandomInteger) -} - -func testGetExpressRouteCircuitResourceGroupAndNameFromSubscription() (string, string, error) { - subscription := strings.ToLower(os.Getenv("ARM_SUBSCRIPTION_ID")) - expressRouteCircuitRG := "" - expressRouteCircuitName := "" - - // As provider status of the Express Route Circuit must be set as provisioned manually by service team, so test cases have to use existing Express Route Circuit for testing. - // Once hashicorp gets the provisioned Express Route Circuit, the same logic for their subscriptions will also be added here. - if strings.HasPrefix(subscription, "67a9759d") || strings.HasPrefix(subscription, "85b3dbca") { - expressRouteCircuitRG = "xz3-test" - expressRouteCircuitName = "tf-er" - } else { - return expressRouteCircuitRG, expressRouteCircuitName, fmt.Errorf("Skipping since test is not running as one of the two valid subscriptions allowed to run ExpressRouteConnection tests") - } - - return expressRouteCircuitRG, expressRouteCircuitName, nil +`, circuitName, circuitRG, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomInteger) } From 96be26431ccd5060f2caa63dca8008bc821472e2 Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Wed, 14 Apr 2021 10:18:49 +0800 Subject: [PATCH 11/32] update tc --- .../network/express_route_connection_resource_test.go | 8 ++++---- website/docs/r/express_route_connection.html.markdown | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/azurerm/internal/services/network/express_route_connection_resource_test.go b/azurerm/internal/services/network/express_route_connection_resource_test.go index 37f8b2974063..94e4b81f70e7 100644 --- a/azurerm/internal/services/network/express_route_connection_resource_test.go +++ b/azurerm/internal/services/network/express_route_connection_resource_test.go @@ -27,10 +27,10 @@ func TestAccExpressRouteConnection(t *testing.T) { // And there can be only one Express Route Connection on the same Express Route Circuit, otherwise tests will conflict if run at the same time. acceptance.RunTestsInSequence(t, map[string]map[string]func(t *testing.T){ "Resource": { - "basic": testAccExpressRouteConnection_basic, - //"requiresImport": testAccExpressRouteConnection_requiresImport, - //"complete": testAccExpressRouteConnection_complete, - //"update": testAccExpressRouteConnection_update, + "basic": testAccExpressRouteConnection_basic, + "requiresImport": testAccExpressRouteConnection_requiresImport, + "complete": testAccExpressRouteConnection_complete, + "update": testAccExpressRouteConnection_update, }, }) } diff --git a/website/docs/r/express_route_connection.html.markdown b/website/docs/r/express_route_connection.html.markdown index 7897774f8d80..65337ba43a57 100644 --- a/website/docs/r/express_route_connection.html.markdown +++ b/website/docs/r/express_route_connection.html.markdown @@ -46,8 +46,8 @@ resource "azurerm_express_route_circuit" "example" { name = "example-expressroutecircuit" location = azurerm_resource_group.example.location resource_group_name = azurerm_resource_group.example.name - service_provider_name = "Microsoft ER Test" - peering_location = "Area51" + service_provider_name = "Equinix" + peering_location = "Silicon Valley" bandwidth_in_mbps = 50 sku { From e7ea6aba503a451e0e998160faf98b0e991bdaed Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Wed, 14 Apr 2021 16:21:36 +0800 Subject: [PATCH 12/32] update code --- .../services/network/express_route_connection_resource_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/azurerm/internal/services/network/express_route_connection_resource_test.go b/azurerm/internal/services/network/express_route_connection_resource_test.go index 94e4b81f70e7..00f941ac55eb 100644 --- a/azurerm/internal/services/network/express_route_connection_resource_test.go +++ b/azurerm/internal/services/network/express_route_connection_resource_test.go @@ -18,6 +18,7 @@ import ( type ExpressRouteConnectionResource struct{} func TestAccExpressRouteConnection(t *testing.T) { + // NOTE: As provider status of the Express Route Circuit must be set as provisioned manually by service team, so test cases have to use existing Express Route Circuit for testing. if os.Getenv("ARM_TEST_DATA_RESOURCE_GROUP") == "" || os.Getenv("ARM_TEST_CIRCUIT_NAME") == "" { t.Skip("Skipping as ARM_TEST_DATA_RESOURCE_GROUP and/or ARM_TEST_CIRCUIT_NAME are not specified") return From 3bd08fe70a87e814ba5013f435b6d71e06348c47 Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Thu, 15 Apr 2021 20:29:13 +0800 Subject: [PATCH 13/32] update code --- .../express_route_connection_resource.go | 51 +++++++++---------- .../internal/services/network/registration.go | 2 +- .../r/express_route_connection.html.markdown | 8 +-- 3 files changed, 29 insertions(+), 32 deletions(-) diff --git a/azurerm/internal/services/network/express_route_connection_resource.go b/azurerm/internal/services/network/express_route_connection_resource.go index 4c714e94c458..18de72942cbd 100644 --- a/azurerm/internal/services/network/express_route_connection_resource.go +++ b/azurerm/internal/services/network/express_route_connection_resource.go @@ -16,12 +16,12 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) -func resourceArmExpressRouteConnection() *schema.Resource { +func resourceExpressRouteConnection() *schema.Resource { return &schema.Resource{ - Create: resourceArmExpressRouteConnectionCreateUpdate, - Read: resourceArmExpressRouteConnectionRead, - Update: resourceArmExpressRouteConnectionCreateUpdate, - Delete: resourceArmExpressRouteConnectionDelete, + Create: resourceExpressRouteConnectionCreateUpdate, + Read: resourceExpressRouteConnectionRead, + Update: resourceExpressRouteConnectionCreateUpdate, + Delete: resourceExpressRouteConnectionDelete, Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(30 * time.Minute), @@ -100,7 +100,7 @@ func resourceArmExpressRouteConnection() *schema.Resource { }, "route_table_ids": { - Type: schema.TypeList, + Type: schema.TypeSet, Optional: true, Computed: true, Elem: &schema.Schema{ @@ -118,12 +118,14 @@ func resourceArmExpressRouteConnection() *schema.Resource { "routing_weight": { Type: schema.TypeInt, Optional: true, + Default: 0, ValidateFunc: validation.IntBetween(0, 32000), }, }, } } -func resourceArmExpressRouteConnectionCreateUpdate(d *schema.ResourceData, meta interface{}) error { + +func resourceExpressRouteConnectionCreateUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*clients.Client).Network.ExpressRouteConnectionsClient ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) defer cancel() @@ -155,6 +157,7 @@ func resourceArmExpressRouteConnectionCreateUpdate(d *schema.ResourceData, meta ExpressRouteCircuitPeering: &network.ExpressRouteCircuitPeeringID{ ID: utils.String(d.Get("express_route_circuit_peering_id").(string)), }, + RoutingWeight: utils.Int32(int32(d.Get("routing_weight").(int))), }, } @@ -166,12 +169,8 @@ func resourceArmExpressRouteConnectionCreateUpdate(d *schema.ResourceData, meta expressRouteConnectionParameters.ExpressRouteConnectionProperties.EnableInternetSecurity = utils.Bool(v.(bool)) } - if v, ok := d.GetOk("routing_weight"); ok { - expressRouteConnectionParameters.ExpressRouteConnectionProperties.RoutingWeight = utils.Int32(int32(v.(int))) - } - if v, ok := d.GetOk("routing"); ok { - expressRouteConnectionParameters.ExpressRouteConnectionProperties.RoutingConfiguration = expandArmExpressRouteConnectionRouting(v.([]interface{})) + expressRouteConnectionParameters.ExpressRouteConnectionProperties.RoutingConfiguration = expandExpressRouteConnectionRouting(v.([]interface{})) } future, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.ExpressRouteGatewayName, name, expressRouteConnectionParameters) @@ -185,10 +184,10 @@ func resourceArmExpressRouteConnectionCreateUpdate(d *schema.ResourceData, meta d.SetId(id.ID()) - return resourceArmExpressRouteConnectionRead(d, meta) + return resourceExpressRouteConnectionRead(d, meta) } -func resourceArmExpressRouteConnectionRead(d *schema.ResourceData, meta interface{}) error { +func resourceExpressRouteConnectionRead(d *schema.ResourceData, meta interface{}) error { client := meta.(*clients.Client).Network.ExpressRouteConnectionsClient ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) defer cancel() @@ -211,6 +210,8 @@ func resourceArmExpressRouteConnectionRead(d *schema.ResourceData, meta interfac d.Set("express_route_gateway_id", parse.NewExpressRouteGatewayID(id.SubscriptionId, id.ResourceGroup, id.ExpressRouteGatewayName).ID()) if props := resp.ExpressRouteConnectionProperties; props != nil { + d.Set("routing_weight", props.RoutingWeight) + if v := props.ExpressRouteCircuitPeering; v != nil { d.Set("express_route_circuit_peering_id", v.ID) } @@ -223,11 +224,7 @@ func resourceArmExpressRouteConnectionRead(d *schema.ResourceData, meta interfac d.Set("enable_internet_security", v) } - if v := props.RoutingWeight; v != nil { - d.Set("routing_weight", v) - } - - if err := d.Set("routing", flattenArmExpressRouteConnectionRouting(props.RoutingConfiguration)); err != nil { + if err := d.Set("routing", flattenExpressRouteConnectionRouting(props.RoutingConfiguration)); err != nil { return fmt.Errorf("setting `routing`: %+v", err) } } @@ -235,7 +232,7 @@ func resourceArmExpressRouteConnectionRead(d *schema.ResourceData, meta interfac return nil } -func resourceArmExpressRouteConnectionDelete(d *schema.ResourceData, meta interface{}) error { +func resourceExpressRouteConnectionDelete(d *schema.ResourceData, meta interface{}) error { client := meta.(*clients.Client).Network.ExpressRouteConnectionsClient ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) defer cancel() @@ -257,7 +254,7 @@ func resourceArmExpressRouteConnectionDelete(d *schema.ResourceData, meta interf return nil } -func expandArmExpressRouteConnectionRouting(input []interface{}) *network.RoutingConfiguration { +func expandExpressRouteConnectionRouting(input []interface{}) *network.RoutingConfiguration { if len(input) == 0 { return &network.RoutingConfiguration{} } @@ -272,13 +269,13 @@ func expandArmExpressRouteConnectionRouting(input []interface{}) *network.Routin } if propagatedRouteTable := v["propagated_route_table"].([]interface{}); len(propagatedRouteTable) != 0 { - result.PropagatedRouteTables = expandArmExpressRouteConnectionPropagatedRouteTable(propagatedRouteTable) + result.PropagatedRouteTables = expandExpressRouteConnectionPropagatedRouteTable(propagatedRouteTable) } return &result } -func expandArmExpressRouteConnectionPropagatedRouteTable(input []interface{}) *network.PropagatedRouteTable { +func expandExpressRouteConnectionPropagatedRouteTable(input []interface{}) *network.PropagatedRouteTable { if len(input) == 0 { return &network.PropagatedRouteTable{} } @@ -291,14 +288,14 @@ func expandArmExpressRouteConnectionPropagatedRouteTable(input []interface{}) *n result.Labels = utils.ExpandStringSlice(labels) } - if routeTableIds := v["route_table_ids"].([]interface{}); len(routeTableIds) != 0 { + if routeTableIds := v["route_table_ids"].(*schema.Set).List(); len(routeTableIds) != 0 { result.Ids = expandIDsToSubResources(routeTableIds) } return &result } -func flattenArmExpressRouteConnectionRouting(input *network.RoutingConfiguration) []interface{} { +func flattenExpressRouteConnectionRouting(input *network.RoutingConfiguration) []interface{} { if input == nil { return []interface{}{} } @@ -311,12 +308,12 @@ func flattenArmExpressRouteConnectionRouting(input *network.RoutingConfiguration return []interface{}{ map[string]interface{}{ "associated_route_table_id": associatedRouteTableId, - "propagated_route_table": flattenArmExpressRouteConnectionPropagatedRouteTable(input.PropagatedRouteTables), + "propagated_route_table": flattenExpressRouteConnectionPropagatedRouteTable(input.PropagatedRouteTables), }, } } -func flattenArmExpressRouteConnectionPropagatedRouteTable(input *network.PropagatedRouteTable) []interface{} { +func flattenExpressRouteConnectionPropagatedRouteTable(input *network.PropagatedRouteTable) []interface{} { if input == nil { return make([]interface{}, 0) } diff --git a/azurerm/internal/services/network/registration.go b/azurerm/internal/services/network/registration.go index 731063bebb1f..7621e4abf185 100644 --- a/azurerm/internal/services/network/registration.go +++ b/azurerm/internal/services/network/registration.go @@ -58,7 +58,7 @@ func (r Registration) SupportedResources() map[string]*schema.Resource { "azurerm_express_route_circuit_authorization": resourceExpressRouteCircuitAuthorization(), "azurerm_express_route_circuit_peering": resourceExpressRouteCircuitPeering(), "azurerm_express_route_circuit": resourceExpressRouteCircuit(), - "azurerm_express_route_connection": resourceArmExpressRouteConnection(), + "azurerm_express_route_connection": resourceExpressRouteConnection(), "azurerm_express_route_gateway": resourceExpressRouteGateway(), "azurerm_ip_group": resourceIpGroup(), "azurerm_local_network_gateway": resourceLocalNetworkGateway(), diff --git a/website/docs/r/express_route_connection.html.markdown b/website/docs/r/express_route_connection.html.markdown index 65337ba43a57..ee9d580de5c1 100644 --- a/website/docs/r/express_route_connection.html.markdown +++ b/website/docs/r/express_route_connection.html.markdown @@ -10,7 +10,7 @@ description: |- Manages an Express Route Connection. -~> **NOTE:** The provider status of the Express Route Circuit must be set as provisioned while creating the Express Route Connection. See more details from https://docs.microsoft.com/en-us/azure/expressroute/expressroute-howto-circuit-portal-resource-manager#send-the-service-key-to-your-connectivity-provider-for-provisioning. +~> **NOTE:** The provider status of the Express Route Circuit must be set as provisioned while creating the Express Route Connection. See more details [here](https://docs.microsoft.com/en-us/azure/expressroute/expressroute-howto-circuit-portal-resource-manager#send-the-service-key-to-your-connectivity-provider-for-provisioning). ## Example Usage @@ -82,9 +82,9 @@ The following arguments are supported: * `name` - (Required) The name which should be used for this Express Route Connection. Changing this forces a new resource to be created. -* `express_route_circuit_peering_id` - (Required) The ID of the Express Route Circuit Peering within this Express Route Connection should be created. Changing this forces a new resource to be created. +* `express_route_circuit_peering_id` - (Required) The ID of the Express Route Circuit Peering within which this Express Route Connection should be created. Changing this forces a new resource to be created. -* `express_route_gateway_id` - (Required) The ID of the Express Route Gateway within this Express Route Connection should be created. Changing this forces a new resource to be created. +* `express_route_gateway_id` - (Required) The ID of the Express Route Gateway within which this Express Route Connection should be created. Changing this forces a new resource to be created. * `authorization_key` - (Optional) The authorization key to establish the Express Route Connection. @@ -92,7 +92,7 @@ The following arguments are supported: * `routing` - (Optional) A `routing` block as defined below. -* `routing_weight` - (Optional) The routing weight associated to the Express Route Connection. Valid values are between `0` and `32000`. +* `routing_weight` - (Optional) The routing weight associated to the Express Route Connection. Valid value is between `0` and `32000`. Defaults to `0`. --- From 7afcec231417118caafff377275785da79b5c4e7 Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Thu, 22 Apr 2021 12:30:15 +0800 Subject: [PATCH 14/32] update code --- .../express_route_connection_resource.go | 9 +- .../express_route_connection_resource_test.go | 91 ++++++++++++++++++- .../r/express_route_connection.html.markdown | 4 + 3 files changed, 95 insertions(+), 9 deletions(-) diff --git a/azurerm/internal/services/network/express_route_connection_resource.go b/azurerm/internal/services/network/express_route_connection_resource.go index 18de72942cbd..c4f26e647b73 100644 --- a/azurerm/internal/services/network/express_route_connection_resource.go +++ b/azurerm/internal/services/network/express_route_connection_resource.go @@ -130,16 +130,15 @@ func resourceExpressRouteConnectionCreateUpdate(d *schema.ResourceData, meta int ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) defer cancel() - name := d.Get("name").(string) expressRouteGatewayId, err := parse.ExpressRouteGatewayID(d.Get("express_route_gateway_id").(string)) if err != nil { return err } - id := parse.NewExpressRouteConnectionID(expressRouteGatewayId.SubscriptionId, expressRouteGatewayId.ResourceGroup, expressRouteGatewayId.Name, name) + id := parse.NewExpressRouteConnectionID(expressRouteGatewayId.SubscriptionId, expressRouteGatewayId.ResourceGroup, expressRouteGatewayId.Name, d.Get("name").(string)) if d.IsNewResource() { - existing, err := client.Get(ctx, id.ResourceGroup, id.ExpressRouteGatewayName, name) + existing, err := client.Get(ctx, id.ResourceGroup, id.ExpressRouteGatewayName, id.Name) if err != nil { if !utils.ResponseWasNotFound(existing.Response) { return fmt.Errorf("checking for existing %s: %+v", id, err) @@ -152,7 +151,7 @@ func resourceExpressRouteConnectionCreateUpdate(d *schema.ResourceData, meta int } expressRouteConnectionParameters := network.ExpressRouteConnection{ - Name: utils.String(d.Get("name").(string)), + Name: utils.String(id.Name), ExpressRouteConnectionProperties: &network.ExpressRouteConnectionProperties{ ExpressRouteCircuitPeering: &network.ExpressRouteCircuitPeeringID{ ID: utils.String(d.Get("express_route_circuit_peering_id").(string)), @@ -173,7 +172,7 @@ func resourceExpressRouteConnectionCreateUpdate(d *schema.ResourceData, meta int expressRouteConnectionParameters.ExpressRouteConnectionProperties.RoutingConfiguration = expandExpressRouteConnectionRouting(v.([]interface{})) } - future, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.ExpressRouteGatewayName, name, expressRouteConnectionParameters) + future, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.ExpressRouteGatewayName, id.Name, expressRouteConnectionParameters) if err != nil { return fmt.Errorf("creating/updating %s: %+v", id, err) } diff --git a/azurerm/internal/services/network/express_route_connection_resource_test.go b/azurerm/internal/services/network/express_route_connection_resource_test.go index 00f941ac55eb..a75ebba8e186 100644 --- a/azurerm/internal/services/network/express_route_connection_resource_test.go +++ b/azurerm/internal/services/network/express_route_connection_resource_test.go @@ -28,10 +28,12 @@ func TestAccExpressRouteConnection(t *testing.T) { // And there can be only one Express Route Connection on the same Express Route Circuit, otherwise tests will conflict if run at the same time. acceptance.RunTestsInSequence(t, map[string]map[string]func(t *testing.T){ "Resource": { - "basic": testAccExpressRouteConnection_basic, - "requiresImport": testAccExpressRouteConnection_requiresImport, - "complete": testAccExpressRouteConnection_complete, - "update": testAccExpressRouteConnection_update, + "basic": testAccExpressRouteConnection_basic, + "requiresImport": testAccExpressRouteConnection_requiresImport, + "complete": testAccExpressRouteConnection_complete, + "update": testAccExpressRouteConnection_update, + "associatedRouteTable": testAccExpressRouteConnection_associatedRouteTable, + "propagatedRouteTable": testAccExpressRouteConnection_propagatedRouteTable, }, }) } @@ -103,6 +105,36 @@ func testAccExpressRouteConnection_update(t *testing.T) { }) } +func testAccExpressRouteConnection_associatedRouteTable(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_express_route_connection", "test") + r := ExpressRouteConnectionResource{} + + data.ResourceSequentialTest(t, r, []resource.TestStep{ + { + Config: r.associatedRouteTable(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func testAccExpressRouteConnection_propagatedRouteTable(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_express_route_connection", "test") + r := ExpressRouteConnectionResource{} + + data.ResourceSequentialTest(t, r, []resource.TestStep{ + { + Config: r.propagatedRouteTable(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func (r ExpressRouteConnectionResource) Exists(ctx context.Context, client *clients.Client, state *terraform.InstanceState) (*bool, error) { expressRouteConnectionClient := client.Network.ExpressRouteConnectionsClient id, err := parse.ExpressRouteConnectionID(state.ID) @@ -176,6 +208,57 @@ resource "azurerm_express_route_connection" "test" { `, r.template(data), data.RandomInteger, data.RandomInteger) } +func (r ExpressRouteConnectionResource) associatedRouteTable(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_virtual_hub_route_table" "test" { + name = "acctest-VHUBRT-%d" + virtual_hub_id = azurerm_virtual_hub.test.id +} + +resource "azurerm_express_route_connection" "test" { + name = "acctest-ExpressRouteConnection-%d" + express_route_gateway_id = azurerm_express_route_gateway.test.id + express_route_circuit_peering_id = azurerm_express_route_circuit_peering.test.id + routing_weight = 2 + authorization_key = "90f8db47-e25b-4b65-a68b-7743ced2a16b" + enable_internet_security = true + + routing { + associated_route_table_id = azurerm_virtual_hub_route_table.test.id + } +} +`, r.template(data), data.RandomInteger, data.RandomInteger) +} + +func (r ExpressRouteConnectionResource) propagatedRouteTable(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_virtual_hub_route_table" "test" { + name = "acctest-VHUBRT-%d" + virtual_hub_id = azurerm_virtual_hub.test.id +} + +resource "azurerm_express_route_connection" "test" { + name = "acctest-ExpressRouteConnection-%d" + express_route_gateway_id = azurerm_express_route_gateway.test.id + express_route_circuit_peering_id = azurerm_express_route_circuit_peering.test.id + routing_weight = 2 + authorization_key = "90f8db47-e25b-4b65-a68b-7743ced2a16b" + enable_internet_security = true + + routing { + propagated_route_table { + labels = ["label1"] + route_table_ids = [azurerm_virtual_hub_route_table.test.id] + } + } +} +`, r.template(data), data.RandomInteger, data.RandomInteger) +} + func (r ExpressRouteConnectionResource) template(data acceptance.TestData) string { circuitRG := os.Getenv("ARM_TEST_DATA_RESOURCE_GROUP") circuitName := os.Getenv("ARM_TEST_CIRCUIT_NAME") diff --git a/website/docs/r/express_route_connection.html.markdown b/website/docs/r/express_route_connection.html.markdown index ee9d580de5c1..c0db3a698581 100644 --- a/website/docs/r/express_route_connection.html.markdown +++ b/website/docs/r/express_route_connection.html.markdown @@ -102,6 +102,8 @@ A `routing` block supports the following: * `propagated_route_table` - (Optional) A `propagated_route_table` block as defined below. +~> **NOTE:** When `routing` isn't specified, `associated_route_table_id` and `propagated_route_table` would be set with the default value. + --- A `propagated_route_table` block supports the following: @@ -110,6 +112,8 @@ A `propagated_route_table` block supports the following: * `route_table_ids` - (Optional) A list of IDs of the Virtual Hub Route Table which are associated with the Virtual Hub Connection. +~> **NOTE:** When `propagated_route_table` isn't specified, `labels` and `route_table_ids` would be set with the default value. + ## Attributes Reference The following attributes are exported: From aaf088f0d8f610fccbc63ede66864e18e508d99e Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Thu, 22 Apr 2021 15:33:43 +0800 Subject: [PATCH 15/32] update code --- .../express_route_connection_resource.go | 1 + .../express_route_connection_resource_test.go | 93 ++----------------- .../r/express_route_connection.html.markdown | 4 - 3 files changed, 7 insertions(+), 91 deletions(-) diff --git a/azurerm/internal/services/network/express_route_connection_resource.go b/azurerm/internal/services/network/express_route_connection_resource.go index c4f26e647b73..a5a3258d72b0 100644 --- a/azurerm/internal/services/network/express_route_connection_resource.go +++ b/azurerm/internal/services/network/express_route_connection_resource.go @@ -68,6 +68,7 @@ func resourceExpressRouteConnection() *schema.Resource { Optional: true, }, + // Note: when `routing` isn't specified, `associated_route_table_id` and `propagated_route_table` would be set with the default value "routing": { Type: schema.TypeList, Optional: true, diff --git a/azurerm/internal/services/network/express_route_connection_resource_test.go b/azurerm/internal/services/network/express_route_connection_resource_test.go index a75ebba8e186..d502020327db 100644 --- a/azurerm/internal/services/network/express_route_connection_resource_test.go +++ b/azurerm/internal/services/network/express_route_connection_resource_test.go @@ -28,12 +28,10 @@ func TestAccExpressRouteConnection(t *testing.T) { // And there can be only one Express Route Connection on the same Express Route Circuit, otherwise tests will conflict if run at the same time. acceptance.RunTestsInSequence(t, map[string]map[string]func(t *testing.T){ "Resource": { - "basic": testAccExpressRouteConnection_basic, - "requiresImport": testAccExpressRouteConnection_requiresImport, - "complete": testAccExpressRouteConnection_complete, - "update": testAccExpressRouteConnection_update, - "associatedRouteTable": testAccExpressRouteConnection_associatedRouteTable, - "propagatedRouteTable": testAccExpressRouteConnection_propagatedRouteTable, + "basic": testAccExpressRouteConnection_basic, + "requiresImport": testAccExpressRouteConnection_requiresImport, + "complete": testAccExpressRouteConnection_complete, + "update": testAccExpressRouteConnection_update, }, }) } @@ -47,6 +45,8 @@ func testAccExpressRouteConnection_basic(t *testing.T) { Config: r.basic(data), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), + check.That("azurerm_express_route_connection.test").Key("routing.0.associated_route_table_id").Exists(), + check.That("azurerm_express_route_connection.test").Key("routing.0.propagated_route_table").Exists(), ), }, data.ImportStep(), @@ -105,36 +105,6 @@ func testAccExpressRouteConnection_update(t *testing.T) { }) } -func testAccExpressRouteConnection_associatedRouteTable(t *testing.T) { - data := acceptance.BuildTestData(t, "azurerm_express_route_connection", "test") - r := ExpressRouteConnectionResource{} - - data.ResourceSequentialTest(t, r, []resource.TestStep{ - { - Config: r.associatedRouteTable(data), - Check: resource.ComposeTestCheckFunc( - check.That(data.ResourceName).ExistsInAzure(r), - ), - }, - data.ImportStep(), - }) -} - -func testAccExpressRouteConnection_propagatedRouteTable(t *testing.T) { - data := acceptance.BuildTestData(t, "azurerm_express_route_connection", "test") - r := ExpressRouteConnectionResource{} - - data.ResourceSequentialTest(t, r, []resource.TestStep{ - { - Config: r.propagatedRouteTable(data), - Check: resource.ComposeTestCheckFunc( - check.That(data.ResourceName).ExistsInAzure(r), - ), - }, - data.ImportStep(), - }) -} - func (r ExpressRouteConnectionResource) Exists(ctx context.Context, client *clients.Client, state *terraform.InstanceState) (*bool, error) { expressRouteConnectionClient := client.Network.ExpressRouteConnectionsClient id, err := parse.ExpressRouteConnectionID(state.ID) @@ -208,57 +178,6 @@ resource "azurerm_express_route_connection" "test" { `, r.template(data), data.RandomInteger, data.RandomInteger) } -func (r ExpressRouteConnectionResource) associatedRouteTable(data acceptance.TestData) string { - return fmt.Sprintf(` -%s - -resource "azurerm_virtual_hub_route_table" "test" { - name = "acctest-VHUBRT-%d" - virtual_hub_id = azurerm_virtual_hub.test.id -} - -resource "azurerm_express_route_connection" "test" { - name = "acctest-ExpressRouteConnection-%d" - express_route_gateway_id = azurerm_express_route_gateway.test.id - express_route_circuit_peering_id = azurerm_express_route_circuit_peering.test.id - routing_weight = 2 - authorization_key = "90f8db47-e25b-4b65-a68b-7743ced2a16b" - enable_internet_security = true - - routing { - associated_route_table_id = azurerm_virtual_hub_route_table.test.id - } -} -`, r.template(data), data.RandomInteger, data.RandomInteger) -} - -func (r ExpressRouteConnectionResource) propagatedRouteTable(data acceptance.TestData) string { - return fmt.Sprintf(` -%s - -resource "azurerm_virtual_hub_route_table" "test" { - name = "acctest-VHUBRT-%d" - virtual_hub_id = azurerm_virtual_hub.test.id -} - -resource "azurerm_express_route_connection" "test" { - name = "acctest-ExpressRouteConnection-%d" - express_route_gateway_id = azurerm_express_route_gateway.test.id - express_route_circuit_peering_id = azurerm_express_route_circuit_peering.test.id - routing_weight = 2 - authorization_key = "90f8db47-e25b-4b65-a68b-7743ced2a16b" - enable_internet_security = true - - routing { - propagated_route_table { - labels = ["label1"] - route_table_ids = [azurerm_virtual_hub_route_table.test.id] - } - } -} -`, r.template(data), data.RandomInteger, data.RandomInteger) -} - func (r ExpressRouteConnectionResource) template(data acceptance.TestData) string { circuitRG := os.Getenv("ARM_TEST_DATA_RESOURCE_GROUP") circuitName := os.Getenv("ARM_TEST_CIRCUIT_NAME") diff --git a/website/docs/r/express_route_connection.html.markdown b/website/docs/r/express_route_connection.html.markdown index c0db3a698581..ee9d580de5c1 100644 --- a/website/docs/r/express_route_connection.html.markdown +++ b/website/docs/r/express_route_connection.html.markdown @@ -102,8 +102,6 @@ A `routing` block supports the following: * `propagated_route_table` - (Optional) A `propagated_route_table` block as defined below. -~> **NOTE:** When `routing` isn't specified, `associated_route_table_id` and `propagated_route_table` would be set with the default value. - --- A `propagated_route_table` block supports the following: @@ -112,8 +110,6 @@ A `propagated_route_table` block supports the following: * `route_table_ids` - (Optional) A list of IDs of the Virtual Hub Route Table which are associated with the Virtual Hub Connection. -~> **NOTE:** When `propagated_route_table` isn't specified, `labels` and `route_table_ids` would be set with the default value. - ## Attributes Reference The following attributes are exported: From 1f14f16bf3d4c8edb4bb0e0f6cad700b5b5bb93d Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Thu, 22 Apr 2021 16:14:50 +0800 Subject: [PATCH 16/32] update code --- .../network/express_route_connection_resource.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/azurerm/internal/services/network/express_route_connection_resource.go b/azurerm/internal/services/network/express_route_connection_resource.go index a5a3258d72b0..53b1642ed6e7 100644 --- a/azurerm/internal/services/network/express_route_connection_resource.go +++ b/azurerm/internal/services/network/express_route_connection_resource.go @@ -212,17 +212,23 @@ func resourceExpressRouteConnectionRead(d *schema.ResourceData, meta interface{} if props := resp.ExpressRouteConnectionProperties; props != nil { d.Set("routing_weight", props.RoutingWeight) + circuitPeeringID := "" if v := props.ExpressRouteCircuitPeering; v != nil { - d.Set("express_route_circuit_peering_id", v.ID) + circuitPeeringID = *v.ID } + d.Set("express_route_circuit_peering_id", circuitPeeringID) + authorizationKey := "" if v := props.AuthorizationKey; v != nil { - d.Set("authorization_key", v) + authorizationKey = *v } + d.Set("authorization_key", authorizationKey) + var enableInternetSecurity bool if v := props.EnableInternetSecurity; v != nil { - d.Set("enable_internet_security", v) + enableInternetSecurity = *v } + d.Set("enable_internet_security", enableInternetSecurity) if err := d.Set("routing", flattenExpressRouteConnectionRouting(props.RoutingConfiguration)); err != nil { return fmt.Errorf("setting `routing`: %+v", err) From 3979ab8e4a580bc1781932bccf8b6a91b6736fab Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Thu, 22 Apr 2021 17:48:00 +0800 Subject: [PATCH 17/32] update code --- .../network/express_route_connection_resource.go | 14 ++------------ .../express_route_connection_resource_test.go | 2 +- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/azurerm/internal/services/network/express_route_connection_resource.go b/azurerm/internal/services/network/express_route_connection_resource.go index 53b1642ed6e7..c6c275e2e5a8 100644 --- a/azurerm/internal/services/network/express_route_connection_resource.go +++ b/azurerm/internal/services/network/express_route_connection_resource.go @@ -211,6 +211,8 @@ func resourceExpressRouteConnectionRead(d *schema.ResourceData, meta interface{} if props := resp.ExpressRouteConnectionProperties; props != nil { d.Set("routing_weight", props.RoutingWeight) + d.Set("authorization_key", props.AuthorizationKey) + d.Set("enable_internet_security", props.EnableInternetSecurity) circuitPeeringID := "" if v := props.ExpressRouteCircuitPeering; v != nil { @@ -218,18 +220,6 @@ func resourceExpressRouteConnectionRead(d *schema.ResourceData, meta interface{} } d.Set("express_route_circuit_peering_id", circuitPeeringID) - authorizationKey := "" - if v := props.AuthorizationKey; v != nil { - authorizationKey = *v - } - d.Set("authorization_key", authorizationKey) - - var enableInternetSecurity bool - if v := props.EnableInternetSecurity; v != nil { - enableInternetSecurity = *v - } - d.Set("enable_internet_security", enableInternetSecurity) - if err := d.Set("routing", flattenExpressRouteConnectionRouting(props.RoutingConfiguration)); err != nil { return fmt.Errorf("setting `routing`: %+v", err) } diff --git a/azurerm/internal/services/network/express_route_connection_resource_test.go b/azurerm/internal/services/network/express_route_connection_resource_test.go index d502020327db..d17eabbec3a8 100644 --- a/azurerm/internal/services/network/express_route_connection_resource_test.go +++ b/azurerm/internal/services/network/express_route_connection_resource_test.go @@ -46,7 +46,7 @@ func testAccExpressRouteConnection_basic(t *testing.T) { Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That("azurerm_express_route_connection.test").Key("routing.0.associated_route_table_id").Exists(), - check.That("azurerm_express_route_connection.test").Key("routing.0.propagated_route_table").Exists(), + check.That("azurerm_express_route_connection.test").Key("routing.0.propagated_route_table.#").HasValue("1"), ), }, data.ImportStep(), From e28c3cd510471c5a1a0ab09368b2f3f46b3d21e9 Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Sat, 24 Apr 2021 00:33:50 +0800 Subject: [PATCH 18/32] update code --- .../express_route_connection_resource.go | 50 ++++++++++++------- .../express_route_connection_resource_test.go | 10 +++- 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/azurerm/internal/services/network/express_route_connection_resource.go b/azurerm/internal/services/network/express_route_connection_resource.go index c6c275e2e5a8..1ad05dd02af0 100644 --- a/azurerm/internal/services/network/express_route_connection_resource.go +++ b/azurerm/internal/services/network/express_route_connection_resource.go @@ -68,26 +68,30 @@ func resourceExpressRouteConnection() *schema.Resource { Optional: true, }, - // Note: when `routing` isn't specified, `associated_route_table_id` and `propagated_route_table` would be set with the default value + // Note: when the `routing` property isn't specified, the `associated_route_table_id` property and the `propagated_route_table` property would be set with the default value. + // As the `routing` property has default value, so it has to add the `ConfigMode` attribute to roll back to the default value after the `routing` property is specified. "routing": { - Type: schema.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + ConfigMode: schema.SchemaConfigModeAttr, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ + // Note: When the `associated_route_table_id` property isn't specified, it has default value. So it has to add the `Computed` attribute to ignore the diff. + // But if the `Computed` attribute is added, it cannot be rolled back to default value after this property is specified. So the `Computed` attribute has to be removed from schema. "associated_route_table_id": { Type: schema.TypeString, Optional: true, - Computed: true, - ValidateFunc: validate.HubRouteTableID, + ValidateFunc: validation.Any(validate.HubRouteTableID, validation.StringIsEmpty), }, "propagated_route_table": { - Type: schema.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + ConfigMode: schema.SchemaConfigModeAttr, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "labels": { @@ -218,9 +222,17 @@ func resourceExpressRouteConnectionRead(d *schema.ResourceData, meta interface{} if v := props.ExpressRouteCircuitPeering; v != nil { circuitPeeringID = *v.ID } - d.Set("express_route_circuit_peering_id", circuitPeeringID) + peeringId, err := parse.ExpressRouteCircuitPeeringID(circuitPeeringID) + if err != nil { + return err + } + d.Set("express_route_circuit_peering_id", peeringId.ID()) - if err := d.Set("routing", flattenExpressRouteConnectionRouting(props.RoutingConfiguration)); err != nil { + routing, err := flattenExpressRouteConnectionRouting(props.RoutingConfiguration) + if err != nil { + return err + } + if err := d.Set("routing", routing); err != nil { return fmt.Errorf("setting `routing`: %+v", err) } } @@ -291,22 +303,26 @@ func expandExpressRouteConnectionPropagatedRouteTable(input []interface{}) *netw return &result } -func flattenExpressRouteConnectionRouting(input *network.RoutingConfiguration) []interface{} { +func flattenExpressRouteConnectionRouting(input *network.RoutingConfiguration) ([]interface{}, error) { if input == nil { - return []interface{}{} + return []interface{}{}, nil } associatedRouteTableId := "" if input.AssociatedRouteTable != nil && input.AssociatedRouteTable.ID != nil { associatedRouteTableId = *input.AssociatedRouteTable.ID } + routeTableId, err := parse.HubRouteTableID(associatedRouteTableId) + if err != nil { + return nil, err + } return []interface{}{ map[string]interface{}{ - "associated_route_table_id": associatedRouteTableId, + "associated_route_table_id": routeTableId.ID(), "propagated_route_table": flattenExpressRouteConnectionPropagatedRouteTable(input.PropagatedRouteTables), }, - } + }, nil } func flattenExpressRouteConnectionPropagatedRouteTable(input *network.PropagatedRouteTable) []interface{} { diff --git a/azurerm/internal/services/network/express_route_connection_resource_test.go b/azurerm/internal/services/network/express_route_connection_resource_test.go index d17eabbec3a8..c4c6c6456056 100644 --- a/azurerm/internal/services/network/express_route_connection_resource_test.go +++ b/azurerm/internal/services/network/express_route_connection_resource_test.go @@ -49,7 +49,10 @@ func testAccExpressRouteConnection_basic(t *testing.T) { check.That("azurerm_express_route_connection.test").Key("routing.0.propagated_route_table.#").HasValue("1"), ), }, - data.ImportStep(), + // Note: When the `associated_route_table_id` property isn't specified, it has default value. + // And the `Computed` attribute cannot be added on this property because it cannot be rolled back to default value after this property is specified. + // So it has to ignore this property here. + data.ImportStep("routing.0.associated_route_table_id"), }) } @@ -94,7 +97,10 @@ func testAccExpressRouteConnection_update(t *testing.T) { check.That(data.ResourceName).ExistsInAzure(r), ), }, - data.ImportStep(), + // Note: When the `associated_route_table_id` property isn't specified, it has default value. + // And the `Computed` attribute cannot be added on this property because it cannot be rolled back to default value after this property is specified. + // So it has to ignore this property here. + data.ImportStep("routing.0.associated_route_table_id"), { Config: r.complete(data), Check: resource.ComposeTestCheckFunc( From 0e765c19e03ed1665d541432554e4e3bb3555730 Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Sat, 24 Apr 2021 00:45:52 +0800 Subject: [PATCH 19/32] update code --- .../network/express_route_connection_resource_test.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/azurerm/internal/services/network/express_route_connection_resource_test.go b/azurerm/internal/services/network/express_route_connection_resource_test.go index c4c6c6456056..d17eabbec3a8 100644 --- a/azurerm/internal/services/network/express_route_connection_resource_test.go +++ b/azurerm/internal/services/network/express_route_connection_resource_test.go @@ -49,10 +49,7 @@ func testAccExpressRouteConnection_basic(t *testing.T) { check.That("azurerm_express_route_connection.test").Key("routing.0.propagated_route_table.#").HasValue("1"), ), }, - // Note: When the `associated_route_table_id` property isn't specified, it has default value. - // And the `Computed` attribute cannot be added on this property because it cannot be rolled back to default value after this property is specified. - // So it has to ignore this property here. - data.ImportStep("routing.0.associated_route_table_id"), + data.ImportStep(), }) } @@ -97,10 +94,7 @@ func testAccExpressRouteConnection_update(t *testing.T) { check.That(data.ResourceName).ExistsInAzure(r), ), }, - // Note: When the `associated_route_table_id` property isn't specified, it has default value. - // And the `Computed` attribute cannot be added on this property because it cannot be rolled back to default value after this property is specified. - // So it has to ignore this property here. - data.ImportStep("routing.0.associated_route_table_id"), + data.ImportStep(), { Config: r.complete(data), Check: resource.ComposeTestCheckFunc( From 95e51829c2556c2771b89570a39b660f58cff044 Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Wed, 16 Jun 2021 13:36:09 +0800 Subject: [PATCH 20/32] update code --- .../services/network/express_route_connection_resource.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azurerm/internal/services/network/express_route_connection_resource.go b/azurerm/internal/services/network/express_route_connection_resource.go index 1ad05dd02af0..9f5b02a155a1 100644 --- a/azurerm/internal/services/network/express_route_connection_resource.go +++ b/azurerm/internal/services/network/express_route_connection_resource.go @@ -4,7 +4,7 @@ import ( "fmt" "time" - "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2020-05-01/network" + "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2020-11-01/network" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" From d1399a3008d222c863ef2a78b307990cd46f329c Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Wed, 16 Jun 2021 15:14:17 +0800 Subject: [PATCH 21/32] update code --- .../express_route_connection_resource.go | 95 +++++++++---------- .../express_route_connection_resource_test.go | 11 ++- .../r/express_route_connection.html.markdown | 14 +-- 3 files changed, 60 insertions(+), 60 deletions(-) diff --git a/azurerm/internal/services/network/express_route_connection_resource.go b/azurerm/internal/services/network/express_route_connection_resource.go index 9f5b02a155a1..eaf8a91270cc 100644 --- a/azurerm/internal/services/network/express_route_connection_resource.go +++ b/azurerm/internal/services/network/express_route_connection_resource.go @@ -5,111 +5,110 @@ import ( "time" "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2020-11-01/network" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/parse" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/validate" - azSchema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/pluginsdk" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) -func resourceExpressRouteConnection() *schema.Resource { - return &schema.Resource{ +func resourceExpressRouteConnection() *pluginsdk.Resource { + return &pluginsdk.Resource{ Create: resourceExpressRouteConnectionCreateUpdate, Read: resourceExpressRouteConnectionRead, Update: resourceExpressRouteConnectionCreateUpdate, Delete: resourceExpressRouteConnectionDelete, - Timeouts: &schema.ResourceTimeout{ - Create: schema.DefaultTimeout(30 * time.Minute), - Read: schema.DefaultTimeout(5 * time.Minute), - Update: schema.DefaultTimeout(30 * time.Minute), - Delete: schema.DefaultTimeout(30 * time.Minute), + Timeouts: &pluginsdk.ResourceTimeout{ + Create: pluginsdk.DefaultTimeout(30 * time.Minute), + Read: pluginsdk.DefaultTimeout(5 * time.Minute), + Update: pluginsdk.DefaultTimeout(30 * time.Minute), + Delete: pluginsdk.DefaultTimeout(30 * time.Minute), }, - Importer: azSchema.ValidateResourceIDPriorToImport(func(id string) error { + Importer: pluginsdk.ImporterValidatingResourceId(func(id string) error { _, err := parse.ExpressRouteConnectionID(id) return err }), - Schema: map[string]*schema.Schema{ + Schema: map[string]*pluginsdk.Schema{ "name": { - Type: schema.TypeString, + Type: pluginsdk.TypeString, Required: true, ForceNew: true, ValidateFunc: validate.ExpressRouteConnectionName, }, "express_route_circuit_peering_id": { - Type: schema.TypeString, + Type: pluginsdk.TypeString, Required: true, ForceNew: true, ValidateFunc: validate.ExpressRouteCircuitPeeringID, }, "express_route_gateway_id": { - Type: schema.TypeString, + Type: pluginsdk.TypeString, Required: true, ForceNew: true, ValidateFunc: validate.ExpressRouteGatewayID, }, "authorization_key": { - Type: schema.TypeString, + Type: pluginsdk.TypeString, Optional: true, ValidateFunc: validation.IsUUID, }, "enable_internet_security": { - Type: schema.TypeBool, + Type: pluginsdk.TypeBool, Optional: true, }, // Note: when the `routing` property isn't specified, the `associated_route_table_id` property and the `propagated_route_table` property would be set with the default value. // As the `routing` property has default value, so it has to add the `ConfigMode` attribute to roll back to the default value after the `routing` property is specified. "routing": { - Type: schema.TypeList, + Type: pluginsdk.TypeList, Optional: true, Computed: true, MaxItems: 1, - ConfigMode: schema.SchemaConfigModeAttr, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ + ConfigMode: pluginsdk.SchemaConfigModeAttr, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ // Note: When the `associated_route_table_id` property isn't specified, it has default value. So it has to add the `Computed` attribute to ignore the diff. // But if the `Computed` attribute is added, it cannot be rolled back to default value after this property is specified. So the `Computed` attribute has to be removed from schema. "associated_route_table_id": { - Type: schema.TypeString, + Type: pluginsdk.TypeString, Optional: true, ValidateFunc: validation.Any(validate.HubRouteTableID, validation.StringIsEmpty), }, "propagated_route_table": { - Type: schema.TypeList, + Type: pluginsdk.TypeList, Optional: true, Computed: true, MaxItems: 1, - ConfigMode: schema.SchemaConfigModeAttr, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ + ConfigMode: pluginsdk.SchemaConfigModeAttr, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ "labels": { - Type: schema.TypeSet, + Type: pluginsdk.TypeSet, Optional: true, Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, ValidateFunc: validation.StringIsNotEmpty, }, }, "route_table_ids": { - Type: schema.TypeSet, + Type: pluginsdk.TypeSet, Optional: true, Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, ValidateFunc: validate.HubRouteTableID, }, }, @@ -121,7 +120,7 @@ func resourceExpressRouteConnection() *schema.Resource { }, "routing_weight": { - Type: schema.TypeInt, + Type: pluginsdk.TypeInt, Optional: true, Default: 0, ValidateFunc: validation.IntBetween(0, 32000), @@ -130,7 +129,7 @@ func resourceExpressRouteConnection() *schema.Resource { } } -func resourceExpressRouteConnectionCreateUpdate(d *schema.ResourceData, meta interface{}) error { +func resourceExpressRouteConnectionCreateUpdate(d *pluginsdk.ResourceData, meta interface{}) error { client := meta.(*clients.Client).Network.ExpressRouteConnectionsClient ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) defer cancel() @@ -155,29 +154,23 @@ func resourceExpressRouteConnectionCreateUpdate(d *schema.ResourceData, meta int } } - expressRouteConnectionParameters := network.ExpressRouteConnection{ + parameters := network.ExpressRouteConnection{ Name: utils.String(id.Name), ExpressRouteConnectionProperties: &network.ExpressRouteConnectionProperties{ ExpressRouteCircuitPeering: &network.ExpressRouteCircuitPeeringID{ ID: utils.String(d.Get("express_route_circuit_peering_id").(string)), }, - RoutingWeight: utils.Int32(int32(d.Get("routing_weight").(int))), + EnableInternetSecurity: utils.Bool(d.Get("enable_internet_security").(bool)), + RoutingConfiguration: expandExpressRouteConnectionRouting(d.Get("routing").([]interface{})), + RoutingWeight: utils.Int32(int32(d.Get("routing_weight").(int))), }, } if v, ok := d.GetOk("authorization_key"); ok { - expressRouteConnectionParameters.ExpressRouteConnectionProperties.AuthorizationKey = utils.String(v.(string)) + parameters.ExpressRouteConnectionProperties.AuthorizationKey = utils.String(v.(string)) } - if v, ok := d.GetOk("enable_internet_security"); ok { - expressRouteConnectionParameters.ExpressRouteConnectionProperties.EnableInternetSecurity = utils.Bool(v.(bool)) - } - - if v, ok := d.GetOk("routing"); ok { - expressRouteConnectionParameters.ExpressRouteConnectionProperties.RoutingConfiguration = expandExpressRouteConnectionRouting(v.([]interface{})) - } - - future, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.ExpressRouteGatewayName, id.Name, expressRouteConnectionParameters) + future, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.ExpressRouteGatewayName, id.Name, parameters) if err != nil { return fmt.Errorf("creating/updating %s: %+v", id, err) } @@ -191,7 +184,7 @@ func resourceExpressRouteConnectionCreateUpdate(d *schema.ResourceData, meta int return resourceExpressRouteConnectionRead(d, meta) } -func resourceExpressRouteConnectionRead(d *schema.ResourceData, meta interface{}) error { +func resourceExpressRouteConnectionRead(d *pluginsdk.ResourceData, meta interface{}) error { client := meta.(*clients.Client).Network.ExpressRouteConnectionsClient ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) defer cancel() @@ -240,7 +233,7 @@ func resourceExpressRouteConnectionRead(d *schema.ResourceData, meta interface{} return nil } -func resourceExpressRouteConnectionDelete(d *schema.ResourceData, meta interface{}) error { +func resourceExpressRouteConnectionDelete(d *pluginsdk.ResourceData, meta interface{}) error { client := meta.(*clients.Client).Network.ExpressRouteConnectionsClient ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) defer cancel() @@ -263,7 +256,7 @@ func resourceExpressRouteConnectionDelete(d *schema.ResourceData, meta interface } func expandExpressRouteConnectionRouting(input []interface{}) *network.RoutingConfiguration { - if len(input) == 0 { + if len(input) == 0 || input[0] == nil { return &network.RoutingConfiguration{} } @@ -284,7 +277,7 @@ func expandExpressRouteConnectionRouting(input []interface{}) *network.RoutingCo } func expandExpressRouteConnectionPropagatedRouteTable(input []interface{}) *network.PropagatedRouteTable { - if len(input) == 0 { + if len(input) == 0 || input[0] == nil { return &network.PropagatedRouteTable{} } @@ -292,11 +285,11 @@ func expandExpressRouteConnectionPropagatedRouteTable(input []interface{}) *netw result := network.PropagatedRouteTable{} - if labels := v["labels"].(*schema.Set).List(); len(labels) != 0 { + if labels := v["labels"].(*pluginsdk.Set).List(); len(labels) != 0 { result.Labels = utils.ExpandStringSlice(labels) } - if routeTableIds := v["route_table_ids"].(*schema.Set).List(); len(routeTableIds) != 0 { + if routeTableIds := v["route_table_ids"].(*pluginsdk.Set).List(); len(routeTableIds) != 0 { result.Ids = expandIDsToSubResources(routeTableIds) } diff --git a/azurerm/internal/services/network/express_route_connection_resource_test.go b/azurerm/internal/services/network/express_route_connection_resource_test.go index d17eabbec3a8..b661a883dd54 100644 --- a/azurerm/internal/services/network/express_route_connection_resource_test.go +++ b/azurerm/internal/services/network/express_route_connection_resource_test.go @@ -7,11 +7,11 @@ import ( "testing" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/terraform" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance/check" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/parse" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/pluginsdk" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -102,10 +102,17 @@ func testAccExpressRouteConnection_update(t *testing.T) { ), }, data.ImportStep(), + { + Config: r.basic(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), }) } -func (r ExpressRouteConnectionResource) Exists(ctx context.Context, client *clients.Client, state *terraform.InstanceState) (*bool, error) { +func (r ExpressRouteConnectionResource) Exists(ctx context.Context, client *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { expressRouteConnectionClient := client.Network.ExpressRouteConnectionsClient id, err := parse.ExpressRouteConnectionID(state.ID) if err != nil { diff --git a/website/docs/r/express_route_connection.html.markdown b/website/docs/r/express_route_connection.html.markdown index ee9d580de5c1..605ff3aab234 100644 --- a/website/docs/r/express_route_connection.html.markdown +++ b/website/docs/r/express_route_connection.html.markdown @@ -82,23 +82,23 @@ The following arguments are supported: * `name` - (Required) The name which should be used for this Express Route Connection. Changing this forces a new resource to be created. -* `express_route_circuit_peering_id` - (Required) The ID of the Express Route Circuit Peering within which this Express Route Connection should be created. Changing this forces a new resource to be created. +* `express_route_circuit_peering_id` - (Required) The ID of the Express Route Circuit Peering that this Express Route Connection connects with. Changing this forces a new resource to be created. -* `express_route_gateway_id` - (Required) The ID of the Express Route Gateway within which this Express Route Connection should be created. Changing this forces a new resource to be created. +* `express_route_gateway_id` - (Required) The ID of the Express Route Gateway that this Express Route Connection connects with. Changing this forces a new resource to be created. * `authorization_key` - (Optional) The authorization key to establish the Express Route Connection. -* `enable_internet_security` - (Optional) Is internet security enabled for this Express Route Connection? +* `enable_internet_security` - (Optional) Is Internet security enabled for this Express Route Connection? * `routing` - (Optional) A `routing` block as defined below. -* `routing_weight` - (Optional) The routing weight associated to the Express Route Connection. Valid value is between `0` and `32000`. Defaults to `0`. +* `routing_weight` - (Optional) The routing weight associated to the Express Route Connection. Possible value is between `0` and `32000`. Defaults to `0`. --- A `routing` block supports the following: -* `associated_route_table_id` - (Optional) The ID of the Virtual Hub Route Table associated with this Virtual Hub Connection. +* `associated_route_table_id` - (Optional) The ID of the Virtual Hub Route Table associated with this Express Route Connection. * `propagated_route_table` - (Optional) A `propagated_route_table` block as defined below. @@ -106,9 +106,9 @@ A `routing` block supports the following: A `propagated_route_table` block supports the following: -* `labels` - (Optional) The list of labels to assign to this Virtual Hub Route Table. +* `labels` - (Optional) The list of labels to logically group route tables. -* `route_table_ids` - (Optional) A list of IDs of the Virtual Hub Route Table which are associated with the Virtual Hub Connection. +* `route_table_ids` - (Optional) A list of IDs of the Virtual Hub Route Table to propagate routes from Express Route Connection to the route table. ## Attributes Reference From c35bfad80fa0980932cfb566dc5f74bb757c16ec Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Wed, 16 Jun 2021 16:44:59 +0800 Subject: [PATCH 22/32] update code --- .../services/network/express_route_connection_resource.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/azurerm/internal/services/network/express_route_connection_resource.go b/azurerm/internal/services/network/express_route_connection_resource.go index eaf8a91270cc..3bdfb3f7cc8b 100644 --- a/azurerm/internal/services/network/express_route_connection_resource.go +++ b/azurerm/internal/services/network/express_route_connection_resource.go @@ -92,6 +92,8 @@ func resourceExpressRouteConnection() *pluginsdk.Resource { MaxItems: 1, ConfigMode: pluginsdk.SchemaConfigModeAttr, Elem: &pluginsdk.Resource{ + // Note: If it only converts one of `labels` and `route_table_ids` to default value, it's not meaningful. + // So they are added `Computed` attribute. Schema: map[string]*pluginsdk.Schema{ "labels": { Type: pluginsdk.TypeSet, From 7806e6fdc2306eade86b75849c7bd53b5fe229be Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Fri, 18 Jun 2021 14:51:52 +0800 Subject: [PATCH 23/32] update code --- .../express_route_connection_resource.go | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/azurerm/internal/services/network/express_route_connection_resource.go b/azurerm/internal/services/network/express_route_connection_resource.go index 3bdfb3f7cc8b..ccf4ee75c115 100644 --- a/azurerm/internal/services/network/express_route_connection_resource.go +++ b/azurerm/internal/services/network/express_route_connection_resource.go @@ -67,33 +67,27 @@ func resourceExpressRouteConnection() *pluginsdk.Resource { Optional: true, }, - // Note: when the `routing` property isn't specified, the `associated_route_table_id` property and the `propagated_route_table` property would be set with the default value. - // As the `routing` property has default value, so it has to add the `ConfigMode` attribute to roll back to the default value after the `routing` property is specified. "routing": { - Type: pluginsdk.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, - ConfigMode: pluginsdk.SchemaConfigModeAttr, + Type: pluginsdk.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, Elem: &pluginsdk.Resource{ Schema: map[string]*pluginsdk.Schema{ - // Note: When the `associated_route_table_id` property isn't specified, it has default value. So it has to add the `Computed` attribute to ignore the diff. - // But if the `Computed` attribute is added, it cannot be rolled back to default value after this property is specified. So the `Computed` attribute has to be removed from schema. "associated_route_table_id": { Type: pluginsdk.TypeString, Optional: true, - ValidateFunc: validation.Any(validate.HubRouteTableID, validation.StringIsEmpty), + Computed: true, + ValidateFunc: validate.HubRouteTableID, + AtLeastOneOf: []string{"routing.0.associated_route_table_id", "routing.0.propagated_route_table"}, }, "propagated_route_table": { - Type: pluginsdk.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, - ConfigMode: pluginsdk.SchemaConfigModeAttr, + Type: pluginsdk.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, Elem: &pluginsdk.Resource{ - // Note: If it only converts one of `labels` and `route_table_ids` to default value, it's not meaningful. - // So they are added `Computed` attribute. Schema: map[string]*pluginsdk.Schema{ "labels": { Type: pluginsdk.TypeSet, @@ -103,19 +97,22 @@ func resourceExpressRouteConnection() *pluginsdk.Resource { Type: pluginsdk.TypeString, ValidateFunc: validation.StringIsNotEmpty, }, + AtLeastOneOf: []string{"routing.0.propagated_route_table.0.labels", "routing.0.propagated_route_table.0.route_table_ids"}, }, "route_table_ids": { - Type: pluginsdk.TypeSet, + Type: pluginsdk.TypeList, Optional: true, Computed: true, Elem: &pluginsdk.Schema{ Type: pluginsdk.TypeString, ValidateFunc: validate.HubRouteTableID, }, + AtLeastOneOf: []string{"routing.0.propagated_route_table.0.labels", "routing.0.propagated_route_table.0.route_table_ids"}, }, }, }, + AtLeastOneOf: []string{"routing.0.associated_route_table_id", "routing.0.propagated_route_table"}, }, }, }, @@ -163,11 +160,14 @@ func resourceExpressRouteConnectionCreateUpdate(d *pluginsdk.ResourceData, meta ID: utils.String(d.Get("express_route_circuit_peering_id").(string)), }, EnableInternetSecurity: utils.Bool(d.Get("enable_internet_security").(bool)), - RoutingConfiguration: expandExpressRouteConnectionRouting(d.Get("routing").([]interface{})), RoutingWeight: utils.Int32(int32(d.Get("routing_weight").(int))), }, } + if v, ok := d.GetOk("routing"); ok { + parameters.ExpressRouteConnectionProperties.RoutingConfiguration = expandExpressRouteConnectionRouting(v.([]interface{})) + } + if v, ok := d.GetOk("authorization_key"); ok { parameters.ExpressRouteConnectionProperties.AuthorizationKey = utils.String(v.(string)) } From 01e0eb3870ce4f54c13a6deab195c741ba3c4887 Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Fri, 18 Jun 2021 21:20:53 +0800 Subject: [PATCH 24/32] update code --- .../express_route_connection_resource.go | 71 ++++++++++++++----- 1 file changed, 55 insertions(+), 16 deletions(-) diff --git a/azurerm/internal/services/network/express_route_connection_resource.go b/azurerm/internal/services/network/express_route_connection_resource.go index ccf4ee75c115..087855f73489 100644 --- a/azurerm/internal/services/network/express_route_connection_resource.go +++ b/azurerm/internal/services/network/express_route_connection_resource.go @@ -17,9 +17,9 @@ import ( func resourceExpressRouteConnection() *pluginsdk.Resource { return &pluginsdk.Resource{ - Create: resourceExpressRouteConnectionCreateUpdate, + Create: resourceExpressRouteConnectionCreate, Read: resourceExpressRouteConnectionRead, - Update: resourceExpressRouteConnectionCreateUpdate, + Update: resourceExpressRouteConnectionUpdate, Delete: resourceExpressRouteConnectionDelete, Timeouts: &pluginsdk.ResourceTimeout{ @@ -128,9 +128,9 @@ func resourceExpressRouteConnection() *pluginsdk.Resource { } } -func resourceExpressRouteConnectionCreateUpdate(d *pluginsdk.ResourceData, meta interface{}) error { +func resourceExpressRouteConnectionCreate(d *pluginsdk.ResourceData, meta interface{}) error { client := meta.(*clients.Client).Network.ExpressRouteConnectionsClient - ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) + ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) defer cancel() expressRouteGatewayId, err := parse.ExpressRouteGatewayID(d.Get("express_route_gateway_id").(string)) @@ -140,19 +140,17 @@ func resourceExpressRouteConnectionCreateUpdate(d *pluginsdk.ResourceData, meta id := parse.NewExpressRouteConnectionID(expressRouteGatewayId.SubscriptionId, expressRouteGatewayId.ResourceGroup, expressRouteGatewayId.Name, d.Get("name").(string)) - if d.IsNewResource() { - existing, err := client.Get(ctx, id.ResourceGroup, id.ExpressRouteGatewayName, id.Name) - if err != nil { - if !utils.ResponseWasNotFound(existing.Response) { - return fmt.Errorf("checking for existing %s: %+v", id, err) - } - } - + existing, err := client.Get(ctx, id.ResourceGroup, id.ExpressRouteGatewayName, id.Name) + if err != nil { if !utils.ResponseWasNotFound(existing.Response) { - return tf.ImportAsExistsError("azurerm_express_route_connection", id.ID()) + return fmt.Errorf("checking for existing %s: %+v", id, err) } } + if !utils.ResponseWasNotFound(existing.Response) { + return tf.ImportAsExistsError("azurerm_express_route_connection", id.ID()) + } + parameters := network.ExpressRouteConnection{ Name: utils.String(id.Name), ExpressRouteConnectionProperties: &network.ExpressRouteConnectionProperties{ @@ -174,11 +172,11 @@ func resourceExpressRouteConnectionCreateUpdate(d *pluginsdk.ResourceData, meta future, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.ExpressRouteGatewayName, id.Name, parameters) if err != nil { - return fmt.Errorf("creating/updating %s: %+v", id, err) + return fmt.Errorf("creating %s: %+v", id, err) } if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { - return fmt.Errorf("waiting for creation/update of %s: %+v", id, err) + return fmt.Errorf("waiting for creation of %s: %+v", id, err) } d.SetId(id.ID()) @@ -235,6 +233,47 @@ func resourceExpressRouteConnectionRead(d *pluginsdk.ResourceData, meta interfac return nil } +func resourceExpressRouteConnectionUpdate(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Network.ExpressRouteConnectionsClient + ctx, cancel := timeouts.ForUpdate(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.ExpressRouteConnectionID(d.Id()) + if err != nil { + return err + } + + parameters := network.ExpressRouteConnection{ + Name: utils.String(id.Name), + ExpressRouteConnectionProperties: &network.ExpressRouteConnectionProperties{ + ExpressRouteCircuitPeering: &network.ExpressRouteCircuitPeeringID{ + ID: utils.String(d.Get("express_route_circuit_peering_id").(string)), + }, + EnableInternetSecurity: utils.Bool(d.Get("enable_internet_security").(bool)), + RoutingWeight: utils.Int32(int32(d.Get("routing_weight").(int))), + }, + } + + if v, ok := d.GetOk("routing"); ok { + parameters.ExpressRouteConnectionProperties.RoutingConfiguration = expandExpressRouteConnectionRouting(v.([]interface{})) + } + + if v, ok := d.GetOk("authorization_key"); ok { + parameters.ExpressRouteConnectionProperties.AuthorizationKey = utils.String(v.(string)) + } + + future, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.ExpressRouteGatewayName, id.Name, parameters) + if err != nil { + return fmt.Errorf("updating %s: %+v", id, err) + } + + if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for update of %s: %+v", id, err) + } + + return resourceExpressRouteConnectionRead(d, meta) +} + func resourceExpressRouteConnectionDelete(d *pluginsdk.ResourceData, meta interface{}) error { client := meta.(*clients.Client).Network.ExpressRouteConnectionsClient ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) @@ -291,7 +330,7 @@ func expandExpressRouteConnectionPropagatedRouteTable(input []interface{}) *netw result.Labels = utils.ExpandStringSlice(labels) } - if routeTableIds := v["route_table_ids"].(*pluginsdk.Set).List(); len(routeTableIds) != 0 { + if routeTableIds := v["route_table_ids"].([]interface{}); len(routeTableIds) != 0 { result.Ids = expandIDsToSubResources(routeTableIds) } From a686b5aa93df4c3f766f68e56e91df6d7e589e15 Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Fri, 18 Jun 2021 23:40:30 +0800 Subject: [PATCH 25/32] update code --- .../network/express_route_connection_resource.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/azurerm/internal/services/network/express_route_connection_resource.go b/azurerm/internal/services/network/express_route_connection_resource.go index 087855f73489..b90a8dbc2ec6 100644 --- a/azurerm/internal/services/network/express_route_connection_resource.go +++ b/azurerm/internal/services/network/express_route_connection_resource.go @@ -158,14 +158,11 @@ func resourceExpressRouteConnectionCreate(d *pluginsdk.ResourceData, meta interf ID: utils.String(d.Get("express_route_circuit_peering_id").(string)), }, EnableInternetSecurity: utils.Bool(d.Get("enable_internet_security").(bool)), + RoutingConfiguration: expandExpressRouteConnectionRouting(d.Get("routing").([]interface{})), RoutingWeight: utils.Int32(int32(d.Get("routing_weight").(int))), }, } - if v, ok := d.GetOk("routing"); ok { - parameters.ExpressRouteConnectionProperties.RoutingConfiguration = expandExpressRouteConnectionRouting(v.([]interface{})) - } - if v, ok := d.GetOk("authorization_key"); ok { parameters.ExpressRouteConnectionProperties.AuthorizationKey = utils.String(v.(string)) } @@ -250,14 +247,11 @@ func resourceExpressRouteConnectionUpdate(d *pluginsdk.ResourceData, meta interf ID: utils.String(d.Get("express_route_circuit_peering_id").(string)), }, EnableInternetSecurity: utils.Bool(d.Get("enable_internet_security").(bool)), + RoutingConfiguration: expandExpressRouteConnectionRouting(d.Get("routing").([]interface{})), RoutingWeight: utils.Int32(int32(d.Get("routing_weight").(int))), }, } - if v, ok := d.GetOk("routing"); ok { - parameters.ExpressRouteConnectionProperties.RoutingConfiguration = expandExpressRouteConnectionRouting(v.([]interface{})) - } - if v, ok := d.GetOk("authorization_key"); ok { parameters.ExpressRouteConnectionProperties.AuthorizationKey = utils.String(v.(string)) } From 36cfbdbe2efff4abd3bc6396c678740591c9bf75 Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Fri, 25 Jun 2021 13:14:21 +0800 Subject: [PATCH 26/32] update code --- .../express_route_connection_resource.go | 2 +- .../express_route_connection_resource_test.go | 21 +++++++++---------- vendor/modules.txt | 7 ++----- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/azurerm/internal/services/network/express_route_connection_resource.go b/azurerm/internal/services/network/express_route_connection_resource.go index b90a8dbc2ec6..dc6caefbbfac 100644 --- a/azurerm/internal/services/network/express_route_connection_resource.go +++ b/azurerm/internal/services/network/express_route_connection_resource.go @@ -5,12 +5,12 @@ import ( "time" "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2020-11-01/network" - "github.com/hashicorp/terraform-plugin-sdk/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/parse" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/validate" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/pluginsdk" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) diff --git a/azurerm/internal/services/network/express_route_connection_resource_test.go b/azurerm/internal/services/network/express_route_connection_resource_test.go index b661a883dd54..3d7f26e080bd 100644 --- a/azurerm/internal/services/network/express_route_connection_resource_test.go +++ b/azurerm/internal/services/network/express_route_connection_resource_test.go @@ -6,7 +6,6 @@ import ( "os" "testing" - "github.com/hashicorp/terraform-plugin-sdk/helper/resource" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance/check" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" @@ -40,10 +39,10 @@ func testAccExpressRouteConnection_basic(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_express_route_connection", "test") r := ExpressRouteConnectionResource{} - data.ResourceSequentialTest(t, r, []resource.TestStep{ + data.ResourceSequentialTest(t, r, []acceptance.TestStep{ { Config: r.basic(data), - Check: resource.ComposeTestCheckFunc( + Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That("azurerm_express_route_connection.test").Key("routing.0.associated_route_table_id").Exists(), check.That("azurerm_express_route_connection.test").Key("routing.0.propagated_route_table.#").HasValue("1"), @@ -57,10 +56,10 @@ func testAccExpressRouteConnection_requiresImport(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_express_route_connection", "test") r := ExpressRouteConnectionResource{} - data.ResourceSequentialTest(t, r, []resource.TestStep{ + data.ResourceSequentialTest(t, r, []acceptance.TestStep{ { Config: r.basic(data), - Check: resource.ComposeTestCheckFunc( + Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), }, @@ -72,10 +71,10 @@ func testAccExpressRouteConnection_complete(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_express_route_connection", "test") r := ExpressRouteConnectionResource{} - data.ResourceSequentialTest(t, r, []resource.TestStep{ + data.ResourceSequentialTest(t, r, []acceptance.TestStep{ { Config: r.complete(data), - Check: resource.ComposeTestCheckFunc( + Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), }, @@ -87,24 +86,24 @@ func testAccExpressRouteConnection_update(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_express_route_connection", "test") r := ExpressRouteConnectionResource{} - data.ResourceSequentialTest(t, r, []resource.TestStep{ + data.ResourceSequentialTest(t, r, []acceptance.TestStep{ { Config: r.basic(data), - Check: resource.ComposeTestCheckFunc( + Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), }, data.ImportStep(), { Config: r.complete(data), - Check: resource.ComposeTestCheckFunc( + Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), }, data.ImportStep(), { Config: r.basic(data), - Check: resource.ComposeTestCheckFunc( + Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), }, diff --git a/vendor/modules.txt b/vendor/modules.txt index 378f23773632..68a2a983f1a8 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -157,14 +157,12 @@ github.com/Azure/go-autorest/tracing # github.com/agext/levenshtein v1.2.2 github.com/agext/levenshtein # github.com/apparentlymart/go-cidr v1.1.0 -## explicit github.com/apparentlymart/go-cidr/cidr # github.com/apparentlymart/go-textseg/v12 v12.0.0 github.com/apparentlymart/go-textseg/v12/textseg # github.com/apparentlymart/go-textseg/v13 v13.0.0 github.com/apparentlymart/go-textseg/v13/textseg # github.com/aws/aws-sdk-go v1.37.0 -## explicit github.com/aws/aws-sdk-go/aws github.com/aws/aws-sdk-go/aws/arn github.com/aws/aws-sdk-go/aws/awserr @@ -300,7 +298,6 @@ github.com/hashicorp/go-uuid ## explicit github.com/hashicorp/go-version # github.com/hashicorp/hcl/v2 v2.8.2 -## explicit github.com/hashicorp/hcl/v2 github.com/hashicorp/hcl/v2/ext/customdecode github.com/hashicorp/hcl/v2/hclsyntax @@ -319,6 +316,8 @@ github.com/hashicorp/terraform-plugin-go/tfprotov5/internal/tfplugin5 github.com/hashicorp/terraform-plugin-go/tfprotov5/internal/toproto github.com/hashicorp/terraform-plugin-go/tfprotov5/server github.com/hashicorp/terraform-plugin-go/tftypes +# github.com/hashicorp/terraform-plugin-sdk v1.17.2 +## explicit # github.com/hashicorp/terraform-plugin-sdk/v2 v2.6.1 ## explicit github.com/hashicorp/terraform-plugin-sdk/v2/diag @@ -486,7 +485,6 @@ golang.org/x/text/transform golang.org/x/text/unicode/bidi golang.org/x/text/unicode/norm # golang.org/x/tools v0.0.0-20201028111035-eafbe7b904eb -## explicit golang.org/x/tools/cmd/goimports golang.org/x/tools/go/ast/astutil golang.org/x/tools/go/gcexportdata @@ -503,7 +501,6 @@ golang.org/x/tools/internal/imports golang.org/x/xerrors golang.org/x/xerrors/internal # google.golang.org/api v0.34.0 -## explicit google.golang.org/api/googleapi google.golang.org/api/googleapi/transport google.golang.org/api/internal From 245599640ec0c1283a299d92f846de71be44d4cc Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Fri, 25 Jun 2021 13:17:15 +0800 Subject: [PATCH 27/32] update code --- vendor/modules.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/vendor/modules.txt b/vendor/modules.txt index 68a2a983f1a8..378f23773632 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -157,12 +157,14 @@ github.com/Azure/go-autorest/tracing # github.com/agext/levenshtein v1.2.2 github.com/agext/levenshtein # github.com/apparentlymart/go-cidr v1.1.0 +## explicit github.com/apparentlymart/go-cidr/cidr # github.com/apparentlymart/go-textseg/v12 v12.0.0 github.com/apparentlymart/go-textseg/v12/textseg # github.com/apparentlymart/go-textseg/v13 v13.0.0 github.com/apparentlymart/go-textseg/v13/textseg # github.com/aws/aws-sdk-go v1.37.0 +## explicit github.com/aws/aws-sdk-go/aws github.com/aws/aws-sdk-go/aws/arn github.com/aws/aws-sdk-go/aws/awserr @@ -298,6 +300,7 @@ github.com/hashicorp/go-uuid ## explicit github.com/hashicorp/go-version # github.com/hashicorp/hcl/v2 v2.8.2 +## explicit github.com/hashicorp/hcl/v2 github.com/hashicorp/hcl/v2/ext/customdecode github.com/hashicorp/hcl/v2/hclsyntax @@ -316,8 +319,6 @@ github.com/hashicorp/terraform-plugin-go/tfprotov5/internal/tfplugin5 github.com/hashicorp/terraform-plugin-go/tfprotov5/internal/toproto github.com/hashicorp/terraform-plugin-go/tfprotov5/server github.com/hashicorp/terraform-plugin-go/tftypes -# github.com/hashicorp/terraform-plugin-sdk v1.17.2 -## explicit # github.com/hashicorp/terraform-plugin-sdk/v2 v2.6.1 ## explicit github.com/hashicorp/terraform-plugin-sdk/v2/diag @@ -485,6 +486,7 @@ golang.org/x/text/transform golang.org/x/text/unicode/bidi golang.org/x/text/unicode/norm # golang.org/x/tools v0.0.0-20201028111035-eafbe7b904eb +## explicit golang.org/x/tools/cmd/goimports golang.org/x/tools/go/ast/astutil golang.org/x/tools/go/gcexportdata @@ -501,6 +503,7 @@ golang.org/x/tools/internal/imports golang.org/x/xerrors golang.org/x/xerrors/internal # google.golang.org/api v0.34.0 +## explicit google.golang.org/api/googleapi google.golang.org/api/googleapi/transport google.golang.org/api/internal From bc8a89e22dd350cf229de71f9dd8ad12ddc9d583 Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Fri, 25 Jun 2021 18:40:34 +0800 Subject: [PATCH 28/32] update code --- .../express_route_connection_resource_test.go | 70 +++++++++++-------- .../r/express_route_connection.html.markdown | 18 +++-- 2 files changed, 52 insertions(+), 36 deletions(-) diff --git a/azurerm/internal/services/network/express_route_connection_resource_test.go b/azurerm/internal/services/network/express_route_connection_resource_test.go index 3d7f26e080bd..c127b50baecf 100644 --- a/azurerm/internal/services/network/express_route_connection_resource_test.go +++ b/azurerm/internal/services/network/express_route_connection_resource_test.go @@ -3,7 +3,6 @@ package network_test import ( "context" "fmt" - "os" "testing" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" @@ -17,14 +16,6 @@ import ( type ExpressRouteConnectionResource struct{} func TestAccExpressRouteConnection(t *testing.T) { - // NOTE: As provider status of the Express Route Circuit must be set as provisioned manually by service team, so test cases have to use existing Express Route Circuit for testing. - if os.Getenv("ARM_TEST_DATA_RESOURCE_GROUP") == "" || os.Getenv("ARM_TEST_CIRCUIT_NAME") == "" { - t.Skip("Skipping as ARM_TEST_DATA_RESOURCE_GROUP and/or ARM_TEST_CIRCUIT_NAME are not specified") - return - } - - // NOTE: As the provider status of the Express Route Circuit only can be manually updated to `Provisioned` by service team, so currently there is only one authorized test data. - // And there can be only one Express Route Connection on the same Express Route Circuit, otherwise tests will conflict if run at the same time. acceptance.RunTestsInSequence(t, map[string]map[string]func(t *testing.T){ "Resource": { "basic": testAccExpressRouteConnection_basic, @@ -160,7 +151,7 @@ func (r ExpressRouteConnectionResource) complete(data acceptance.TestData) strin %s resource "azurerm_virtual_hub_route_table" "test" { - name = "acctest-VHUBRT-%d" + name = "acctest-vhubrt-%d" virtual_hub_id = azurerm_virtual_hub.test.id } @@ -185,18 +176,42 @@ resource "azurerm_express_route_connection" "test" { } func (r ExpressRouteConnectionResource) template(data acceptance.TestData) string { - circuitRG := os.Getenv("ARM_TEST_DATA_RESOURCE_GROUP") - circuitName := os.Getenv("ARM_TEST_CIRCUIT_NAME") - return fmt.Sprintf(` provider "azurerm" { features {} } +resource "azurerm_resource_group" "test" { + name = "acctestRG-erconnection-%d" + location = "%s" +} + +resource "azurerm_express_route_port" "test" { + name = "acctest-erp-%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + peering_location = "Equinix-Seattle-SE2" + bandwidth_in_gbps = 10 + encapsulation = "Dot1Q" +} + +resource "azurerm_express_route_circuit" "test" { + name = "acctest-erc-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + express_route_port_id = azurerm_express_route_port.test.id + bandwidth_in_gbps = 5 + + sku { + tier = "Standard" + family = "MeteredData" + } +} + resource "azurerm_express_route_circuit_peering" "test" { peering_type = "AzurePrivatePeering" - express_route_circuit_name = "%s" - resource_group_name = "%s" + express_route_circuit_name = azurerm_express_route_circuit.test.name + resource_group_name = azurerm_resource_group.test.name shared_key = "ItsASecret" peer_asn = 100 primary_peer_address_prefix = "192.168.1.0/30" @@ -204,31 +219,26 @@ resource "azurerm_express_route_circuit_peering" "test" { vlan_id = 100 } -resource "azurerm_resource_group" "test2" { - name = "acctestRG-erconnection-%d" - location = "%s" -} - resource "azurerm_virtual_wan" "test" { - name = "acctest-VWAN-%d" - resource_group_name = azurerm_resource_group.test2.name - location = azurerm_resource_group.test2.location + name = "acctest-vwan-%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location } resource "azurerm_virtual_hub" "test" { - name = "acctest-VHUB-%d" - resource_group_name = azurerm_resource_group.test2.name - location = azurerm_resource_group.test2.location + name = "acctest-vhub-%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location virtual_wan_id = azurerm_virtual_wan.test.id address_prefix = "10.0.1.0/24" } resource "azurerm_express_route_gateway" "test" { - name = "acctest-ERGW-%d" - resource_group_name = azurerm_resource_group.test2.name - location = azurerm_resource_group.test2.location + name = "acctest-ergw-%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location virtual_hub_id = azurerm_virtual_hub.test.id scale_units = 1 } -`, circuitName, circuitRG, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomInteger) +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomInteger, data.RandomInteger, data.RandomInteger) } diff --git a/website/docs/r/express_route_connection.html.markdown b/website/docs/r/express_route_connection.html.markdown index 605ff3aab234..62dac20b0ab1 100644 --- a/website/docs/r/express_route_connection.html.markdown +++ b/website/docs/r/express_route_connection.html.markdown @@ -42,20 +42,26 @@ resource "azurerm_express_route_gateway" "example" { scale_units = 1 } +resource "azurerm_express_route_port" "example" { + name = "example-erp" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + peering_location = "Equinix-Seattle-SE2" + bandwidth_in_gbps = 10 + encapsulation = "Dot1Q" +} + resource "azurerm_express_route_circuit" "example" { - name = "example-expressroutecircuit" + name = "example-erc" location = azurerm_resource_group.example.location resource_group_name = azurerm_resource_group.example.name - service_provider_name = "Equinix" - peering_location = "Silicon Valley" - bandwidth_in_mbps = 50 + express_route_port_id = azurerm_express_route_port.example.id + bandwidth_in_gbps = 5 sku { tier = "Standard" family = "MeteredData" } - - allow_classic_operations = false } resource "azurerm_express_route_circuit_peering" "example" { From faaf68bf258652f8f06870548bccf2ebb9de2c41 Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Fri, 25 Jun 2021 23:14:57 +0800 Subject: [PATCH 29/32] update code --- .../express_route_connection_resource_test.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/azurerm/internal/services/network/express_route_connection_resource_test.go b/azurerm/internal/services/network/express_route_connection_resource_test.go index c127b50baecf..674648aba34b 100644 --- a/azurerm/internal/services/network/express_route_connection_resource_test.go +++ b/azurerm/internal/services/network/express_route_connection_resource_test.go @@ -129,6 +129,8 @@ resource "azurerm_express_route_connection" "test" { name = "acctest-ExpressRouteConnection-%d" express_route_gateway_id = azurerm_express_route_gateway.test.id express_route_circuit_peering_id = azurerm_express_route_circuit_peering.test.id + + depends_on = [azurerm_virtual_hub_route_table.test] } `, r.template(data), data.RandomInteger) } @@ -150,11 +152,6 @@ func (r ExpressRouteConnectionResource) complete(data acceptance.TestData) strin return fmt.Sprintf(` %s -resource "azurerm_virtual_hub_route_table" "test" { - name = "acctest-vhubrt-%d" - virtual_hub_id = azurerm_virtual_hub.test.id -} - resource "azurerm_express_route_connection" "test" { name = "acctest-ExpressRouteConnection-%d" express_route_gateway_id = azurerm_express_route_gateway.test.id @@ -172,7 +169,7 @@ resource "azurerm_express_route_connection" "test" { } } } -`, r.template(data), data.RandomInteger, data.RandomInteger) +`, r.template(data), data.RandomInteger) } func (r ExpressRouteConnectionResource) template(data acceptance.TestData) string { @@ -240,5 +237,10 @@ resource "azurerm_express_route_gateway" "test" { virtual_hub_id = azurerm_virtual_hub.test.id scale_units = 1 } -`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomInteger, data.RandomInteger, data.RandomInteger) + +resource "azurerm_virtual_hub_route_table" "test" { + name = "acctest-vhubrt-%d" + virtual_hub_id = azurerm_virtual_hub.test.id +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomInteger, data.RandomInteger, data.RandomInteger, data.RandomInteger) } From d6f7c20de569f81e2e0811580db8245daca6a35d Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Sat, 26 Jun 2021 18:03:37 +0800 Subject: [PATCH 30/32] update code --- .../services/network/express_route_connection_resource_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azurerm/internal/services/network/express_route_connection_resource_test.go b/azurerm/internal/services/network/express_route_connection_resource_test.go index 674648aba34b..36ba233f4b48 100644 --- a/azurerm/internal/services/network/express_route_connection_resource_test.go +++ b/azurerm/internal/services/network/express_route_connection_resource_test.go @@ -200,7 +200,7 @@ resource "azurerm_express_route_circuit" "test" { bandwidth_in_gbps = 5 sku { - tier = "Standard" + tier = "Premium" family = "MeteredData" } } From 1ec13657d2388a98deaa8b9d078634e1bf4017e6 Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Sat, 26 Jun 2021 19:33:58 +0800 Subject: [PATCH 31/32] update code --- .../network/express_route_connection_resource_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/azurerm/internal/services/network/express_route_connection_resource_test.go b/azurerm/internal/services/network/express_route_connection_resource_test.go index 36ba233f4b48..26c739982318 100644 --- a/azurerm/internal/services/network/express_route_connection_resource_test.go +++ b/azurerm/internal/services/network/express_route_connection_resource_test.go @@ -18,10 +18,10 @@ type ExpressRouteConnectionResource struct{} func TestAccExpressRouteConnection(t *testing.T) { acceptance.RunTestsInSequence(t, map[string]map[string]func(t *testing.T){ "Resource": { - "basic": testAccExpressRouteConnection_basic, - "requiresImport": testAccExpressRouteConnection_requiresImport, - "complete": testAccExpressRouteConnection_complete, - "update": testAccExpressRouteConnection_update, + "basic": testAccExpressRouteConnection_basic, + //"requiresImport": testAccExpressRouteConnection_requiresImport, + //"complete": testAccExpressRouteConnection_complete, + //"update": testAccExpressRouteConnection_update, }, }) } @@ -187,7 +187,7 @@ resource "azurerm_express_route_port" "test" { name = "acctest-erp-%d" resource_group_name = azurerm_resource_group.test.name location = azurerm_resource_group.test.location - peering_location = "Equinix-Seattle-SE2" + peering_location = "CDC-Canberra" bandwidth_in_gbps = 10 encapsulation = "Dot1Q" } From 5504095f0e441b42866302d9de7351124713775a Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Sat, 26 Jun 2021 21:10:42 +0800 Subject: [PATCH 32/32] update code --- .../network/express_route_connection_resource_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/azurerm/internal/services/network/express_route_connection_resource_test.go b/azurerm/internal/services/network/express_route_connection_resource_test.go index 26c739982318..204625ac30bb 100644 --- a/azurerm/internal/services/network/express_route_connection_resource_test.go +++ b/azurerm/internal/services/network/express_route_connection_resource_test.go @@ -18,10 +18,10 @@ type ExpressRouteConnectionResource struct{} func TestAccExpressRouteConnection(t *testing.T) { acceptance.RunTestsInSequence(t, map[string]map[string]func(t *testing.T){ "Resource": { - "basic": testAccExpressRouteConnection_basic, - //"requiresImport": testAccExpressRouteConnection_requiresImport, - //"complete": testAccExpressRouteConnection_complete, - //"update": testAccExpressRouteConnection_update, + "basic": testAccExpressRouteConnection_basic, + "requiresImport": testAccExpressRouteConnection_requiresImport, + "complete": testAccExpressRouteConnection_complete, + "update": testAccExpressRouteConnection_update, }, }) }