diff --git a/azurerm/internal/services/network/client/client.go b/azurerm/internal/services/network/client/client.go index a9ed0649cebe..61665821605e 100644 --- a/azurerm/internal/services/network/client/client.go +++ b/azurerm/internal/services/network/client/client.go @@ -13,6 +13,7 @@ type Client struct { DDOSProtectionPlansClient *network.DdosProtectionPlansClient ExpressRouteAuthsClient *network.ExpressRouteCircuitAuthorizationsClient ExpressRouteCircuitsClient *network.ExpressRouteCircuitsClient + ExpressRouteConnectionsClient *network.ExpressRouteConnectionsClient ExpressRouteGatewaysClient *network.ExpressRouteGatewaysClient ExpressRoutePeeringsClient *network.ExpressRouteCircuitPeeringsClient ExpressRoutePortsClient *network.ExpressRoutePortsClient @@ -81,6 +82,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) @@ -221,6 +225,7 @@ func NewClient(o *common.ClientOptions) *Client { DDOSProtectionPlansClient: &DDOSProtectionPlansClient, ExpressRouteAuthsClient: &ExpressRouteAuthsClient, ExpressRouteCircuitsClient: &ExpressRouteCircuitsClient, + ExpressRouteConnectionsClient: &ExpressRouteConnectionsClient, ExpressRouteGatewaysClient: &ExpressRouteGatewaysClient, ExpressRoutePeeringsClient: &ExpressRoutePeeringsClient, ExpressRoutePortsClient: &ExpressRoutePortsClient, 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..dc6caefbbfac --- /dev/null +++ b/azurerm/internal/services/network/express_route_connection_resource.go @@ -0,0 +1,377 @@ +package network + +import ( + "fmt" + "time" + + "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2020-11-01/network" + "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" +) + +func resourceExpressRouteConnection() *pluginsdk.Resource { + return &pluginsdk.Resource{ + Create: resourceExpressRouteConnectionCreate, + Read: resourceExpressRouteConnectionRead, + Update: resourceExpressRouteConnectionUpdate, + Delete: resourceExpressRouteConnectionDelete, + + 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: pluginsdk.ImporterValidatingResourceId(func(id string) error { + _, err := parse.ExpressRouteConnectionID(id) + return err + }), + + Schema: map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.ExpressRouteConnectionName, + }, + + "express_route_circuit_peering_id": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.ExpressRouteCircuitPeeringID, + }, + + "express_route_gateway_id": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.ExpressRouteGatewayID, + }, + + "authorization_key": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.IsUUID, + }, + + "enable_internet_security": { + Type: pluginsdk.TypeBool, + Optional: true, + }, + + "routing": { + Type: pluginsdk.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "associated_route_table_id": { + Type: pluginsdk.TypeString, + Optional: true, + 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, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "labels": { + Type: pluginsdk.TypeSet, + Optional: true, + Computed: true, + Elem: &pluginsdk.Schema{ + 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.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"}, + }, + }, + }, + }, + + "routing_weight": { + Type: pluginsdk.TypeInt, + Optional: true, + Default: 0, + ValidateFunc: validation.IntBetween(0, 32000), + }, + }, + } +} + +func resourceExpressRouteConnectionCreate(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Network.ExpressRouteConnectionsClient + ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) + defer cancel() + + 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, d.Get("name").(string)) + + 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) + } + } + + 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{ + ExpressRouteCircuitPeering: &network.ExpressRouteCircuitPeeringID{ + 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("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("creating %s: %+v", id, err) + } + + if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for creation of %s: %+v", id, err) + } + + d.SetId(id.ID()) + + return resourceExpressRouteConnectionRead(d, meta) +} + +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() + + 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) { + d.SetId("") + return nil + } + return fmt.Errorf("retrieving %s: %+v", *id, err) + } + + d.Set("name", id.Name) + 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) + d.Set("authorization_key", props.AuthorizationKey) + d.Set("enable_internet_security", props.EnableInternetSecurity) + + circuitPeeringID := "" + if v := props.ExpressRouteCircuitPeering; v != nil { + circuitPeeringID = *v.ID + } + peeringId, err := parse.ExpressRouteCircuitPeeringID(circuitPeeringID) + if err != nil { + return err + } + d.Set("express_route_circuit_peering_id", peeringId.ID()) + + 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) + } + } + + 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)), + RoutingConfiguration: expandExpressRouteConnectionRouting(d.Get("routing").([]interface{})), + RoutingWeight: utils.Int32(int32(d.Get("routing_weight").(int))), + }, + } + + 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) + 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 %s: %+v", *id, err) + } + + if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for deletion of %s: %+v", *id, err) + } + + return nil +} + +func expandExpressRouteConnectionRouting(input []interface{}) *network.RoutingConfiguration { + if len(input) == 0 || input[0] == nil { + 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 propagatedRouteTable := v["propagated_route_table"].([]interface{}); len(propagatedRouteTable) != 0 { + result.PropagatedRouteTables = expandExpressRouteConnectionPropagatedRouteTable(propagatedRouteTable) + } + + return &result +} + +func expandExpressRouteConnectionPropagatedRouteTable(input []interface{}) *network.PropagatedRouteTable { + if len(input) == 0 || input[0] == nil { + return &network.PropagatedRouteTable{} + } + + v := input[0].(map[string]interface{}) + + result := network.PropagatedRouteTable{} + + if labels := v["labels"].(*pluginsdk.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 flattenExpressRouteConnectionRouting(input *network.RoutingConfiguration) ([]interface{}, error) { + if input == nil { + 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": routeTableId.ID(), + "propagated_route_table": flattenExpressRouteConnectionPropagatedRouteTable(input.PropagatedRouteTables), + }, + }, nil +} + +func flattenExpressRouteConnectionPropagatedRouteTable(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, + }, + } +} 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..204625ac30bb --- /dev/null +++ b/azurerm/internal/services/network/express_route_connection_resource_test.go @@ -0,0 +1,246 @@ +package network_test + +import ( + "context" + "fmt" + "testing" + + "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" +) + +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, + }, + }) +} + +func testAccExpressRouteConnection_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_express_route_connection", "test") + r := ExpressRouteConnectionResource{} + + data.ResourceSequentialTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + 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"), + ), + }, + data.ImportStep(), + }) +} + +func testAccExpressRouteConnection_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_express_route_connection", "test") + r := ExpressRouteConnectionResource{} + + data.ResourceSequentialTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.RequiresImportErrorStep(r.requiresImport), + }) +} + +func testAccExpressRouteConnection_complete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_express_route_connection", "test") + r := ExpressRouteConnectionResource{} + + data.ResourceSequentialTest(t, r, []acceptance.TestStep{ + { + Config: r.complete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func testAccExpressRouteConnection_update(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_express_route_connection", "test") + r := ExpressRouteConnectionResource{} + + data.ResourceSequentialTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.complete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +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 { + 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) 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 + + depends_on = [azurerm_virtual_hub_route_table.test] +} +`, r.template(data), data.RandomInteger) +} + +func (r ExpressRouteConnectionResource) requiresImport(data acceptance.TestData) string { + config := r.basic(data) + 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) 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 + + 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), data.RandomInteger) +} + +func (r ExpressRouteConnectionResource) template(data acceptance.TestData) string { + 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 = "CDC-Canberra" + 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 = "Premium" + family = "MeteredData" + } +} + +resource "azurerm_express_route_circuit_peering" "test" { + peering_type = "AzurePrivatePeering" + 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" + 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.test.name + location = azurerm_resource_group.test.location +} + +resource "azurerm_virtual_hub" "test" { + 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.test.name + location = azurerm_resource_group.test.location + virtual_hub_id = azurerm_virtual_hub.test.id + scale_units = 1 +} + +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) +} 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..eb55c135d97e --- /dev/null +++ b/azurerm/internal/services/network/parse/express_route_circuit_peering.go @@ -0,0 +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 { + 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, err + } + + resourceId := ExpressRouteCircuitPeeringId{ + SubscriptionId: id.SubscriptionID, + ResourceGroup: id.ResourceGroup, + } + + 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 resourceId.ExpressRouteCircuitName, err = id.PopSegment("expressRouteCircuits"); err != nil { + return nil, err + } + if resourceId.PeeringName, err = id.PopSegment("peerings"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + 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 new file mode 100644 index 000000000000..39b00756af4b --- /dev/null +++ b/azurerm/internal/services/network/parse/express_route_circuit_peering_test.go @@ -0,0 +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 { + Input string + Error bool + Expected *ExpressRouteCircuitPeeringId + }{ + + { + // empty + Input: "", + Error: true, + }, + + { + // missing SubscriptionId + Input: "/", + Error: true, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Error: true, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", + Error: true, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", + Error: true, + }, + + { + // missing ExpressRouteCircuitName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/", + Error: true, + }, + + { + // missing value for ExpressRouteCircuitName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteCircuits/", + Error: true, + }, + + { + // missing PeeringName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteCircuits/erCircuit1/", + Error: true, + }, + + { + // 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{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resGroup1", + ExpressRouteCircuitName: "erCircuit1", + PeeringName: "peering1", + }, + }, + + { + // 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.Input) + + actual, err := ExpressRouteCircuitPeeringID(v.Input) + if err != nil { + if v.Error { + continue + } + + 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.ExpressRouteCircuitName != v.Expected.ExpressRouteCircuitName { + t.Fatalf("Expected %q but got %q for ExpressRouteCircuitName", v.Expected.ExpressRouteCircuitName, actual.ExpressRouteCircuitName) + } + 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_connection.go b/azurerm/internal/services/network/parse/express_route_connection.go new file mode 100644 index 000000000000..3ffd2f65972b --- /dev/null +++ b/azurerm/internal/services/network/parse/express_route_connection.go @@ -0,0 +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 ExpressRouteConnectionId struct { + SubscriptionId string + ResourceGroup string + ExpressRouteGatewayName string + Name string +} + +func NewExpressRouteConnectionID(subscriptionId, resourceGroup, expressRouteGatewayName, name string) ExpressRouteConnectionId { + return ExpressRouteConnectionId{ + SubscriptionId: subscriptionId, + ResourceGroup: resourceGroup, + ExpressRouteGatewayName: expressRouteGatewayName, + Name: 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, err + } + + resourceId := ExpressRouteConnectionId{ + SubscriptionId: id.SubscriptionID, + ResourceGroup: id.ResourceGroup, + } + + 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 resourceId.ExpressRouteGatewayName, err = id.PopSegment("expressRouteGateways"); err != nil { + return nil, err + } + if resourceId.Name, err = id.PopSegment("expressRouteConnections"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + 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 new file mode 100644 index 000000000000..30e902fe5c72 --- /dev/null +++ b/azurerm/internal/services/network/parse/express_route_connection_test.go @@ -0,0 +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 = ExpressRouteConnectionId{} + +func TestExpressRouteConnectionIDFormatter(t *testing.T) { + 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) + } +} + +func TestExpressRouteConnectionID(t *testing.T) { + testData := []struct { + Input string + Error bool + Expected *ExpressRouteConnectionId + }{ + + { + // empty + Input: "", + Error: true, + }, + + { + // missing SubscriptionId + Input: "/", + Error: true, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Error: true, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", + Error: true, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", + Error: true, + }, + + { + // 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{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resGroup1", + ExpressRouteGatewayName: "ergw1", + Name: "erConnection1", + }, + }, + + { + // 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.Input) + + actual, err := ExpressRouteConnectionID(v.Input) + if err != nil { + if v.Error { + continue + } + + 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/parse/express_route_gateway.go b/azurerm/internal/services/network/parse/express_route_gateway.go new file mode 100644 index 000000000000..c32da2f41fbf --- /dev/null +++ b/azurerm/internal/services/network/parse/express_route_gateway.go @@ -0,0 +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 ExpressRouteGatewayId struct { + SubscriptionId string + ResourceGroup string + Name string +} + +func NewExpressRouteGatewayID(subscriptionId, resourceGroup, name string) ExpressRouteGatewayId { + return ExpressRouteGatewayId{ + SubscriptionId: subscriptionId, + ResourceGroup: resourceGroup, + Name: 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, err + } + + resourceId := ExpressRouteGatewayId{ + SubscriptionId: id.SubscriptionID, + ResourceGroup: id.ResourceGroup, + } + + 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 resourceId.Name, err = id.PopSegment("expressRouteGateways"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + 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 new file mode 100644 index 000000000000..4e06c9110f3c --- /dev/null +++ b/azurerm/internal/services/network/parse/express_route_gateway_test.go @@ -0,0 +1,112 @@ +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 = ExpressRouteGatewayId{} + +func TestExpressRouteGatewayIDFormatter(t *testing.T) { + 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) + } +} + +func TestExpressRouteGatewayID(t *testing.T) { + testData := []struct { + Input string + Error bool + Expected *ExpressRouteGatewayId + }{ + + { + // empty + Input: "", + Error: true, + }, + + { + // missing SubscriptionId + Input: "/", + Error: true, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Error: true, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", + Error: true, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", + Error: true, + }, + + { + // missing Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/", + Error: true, + }, + + { + // missing value for Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteGateways/", + Error: true, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/expressRouteGateways/ergw1", + Expected: &ExpressRouteGatewayId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resGroup1", + Name: "ergw1", + }, + }, + + { + // 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.Input) + + actual, err := ExpressRouteGatewayID(v.Input) + if err != nil { + if v.Error { + continue + } + + 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/registration.go b/azurerm/internal/services/network/registration.go index f2021dca033b..13561cd15495 100644 --- a/azurerm/internal/services/network/registration.go +++ b/azurerm/internal/services/network/registration.go @@ -58,6 +58,7 @@ func (r Registration) SupportedResources() map[string]*pluginsdk.Resource { "azurerm_express_route_circuit_authorization": resourceExpressRouteCircuitAuthorization(), "azurerm_express_route_circuit_peering": resourceExpressRouteCircuitPeering(), "azurerm_express_route_circuit": resourceExpressRouteCircuit(), + "azurerm_express_route_connection": resourceExpressRouteConnection(), "azurerm_express_route_gateway": resourceExpressRouteGateway(), "azurerm_express_route_port": resourceArmExpressRoutePort(), "azurerm_ip_group": resourceIpGroup(), diff --git a/azurerm/internal/services/network/resourceids.go b/azurerm/internal/services/network/resourceids.go index f7d1b3aa92a9..f3f9df53f0c7 100644 --- a/azurerm/internal/services/network/resourceids.go +++ b/azurerm/internal/services/network/resourceids.go @@ -60,3 +60,8 @@ 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=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 +//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_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_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/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/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/website/docs/r/express_route_connection.html.markdown b/website/docs/r/express_route_connection.html.markdown new file mode 100644 index 000000000000..62dac20b0ab1 --- /dev/null +++ b/website/docs/r/express_route_connection.html.markdown @@ -0,0 +1,140 @@ +--- +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. + +~> **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 + +```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_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-erc" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + express_route_port_id = azurerm_express_route_port.example.id + bandwidth_in_gbps = 5 + + sku { + tier = "Standard" + family = "MeteredData" + } +} + +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 = "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_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_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 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? + +* `routing` - (Optional) A `routing` block as defined below. + +* `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 Express Route Connection. + +* `propagated_route_table` - (Optional) A `propagated_route_table` block as defined below. + +--- + +A `propagated_route_table` block supports the following: + +* `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 to propagate routes from Express Route Connection to the route table. + +## 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 +```