From 80b269150b5d5aa34503a633658f7e68f1a28e39 Mon Sep 17 00:00:00 2001 From: Tim Hogarty Date: Wed, 1 Nov 2023 23:39:17 -0700 Subject: [PATCH 1/4] Add routing protocols resource to terraform provider --- .gitignore | 4 +- .../equinix_fabric_routing_protocol.md | 4 +- .../equinix_fabric_routing_protocols.md | 205 +++++++ .../equinix_fabric_routing_protocol.md | 4 +- .../equinix_fabric_routing_protocols.md | 230 ++++++++ .../data_source_fabric_routing_protocols.go | 64 +++ equinix/errors.go | 2 +- equinix/fabric_mapping_helper.go | 169 +++--- equinix/fabric_routing_protocols_schema.go | 311 +++++++++++ equinix/provider.go | 2 + equinix/resource_fabric_routing_protocol.go | 8 +- equinix/resource_fabric_routing_protocols.go | 498 ++++++++++++++++++ ...ource_fabric_routing_protocols_acc_test.go | 105 ++++ .../routing-protocols/README.md | 37 ++ .../routing-protocols/main.tf | 39 ++ .../routing-protocols/terraform.tf | 8 + .../terraform.tfvars.example | 17 + .../routing-protocols/variables.tf | 16 + 18 files changed, 1622 insertions(+), 101 deletions(-) create mode 100644 docs/data-sources/equinix_fabric_routing_protocols.md create mode 100644 docs/resources/equinix_fabric_routing_protocols.md create mode 100644 equinix/data_source_fabric_routing_protocols.go create mode 100644 equinix/fabric_routing_protocols_schema.go create mode 100644 equinix/resource_fabric_routing_protocols.go create mode 100644 equinix/resource_fabric_routing_protocols_acc_test.go create mode 100644 examples/fabric/v4/cloudRouterConnectivity/routing-protocols/README.md create mode 100644 examples/fabric/v4/cloudRouterConnectivity/routing-protocols/main.tf create mode 100644 examples/fabric/v4/cloudRouterConnectivity/routing-protocols/terraform.tf create mode 100644 examples/fabric/v4/cloudRouterConnectivity/routing-protocols/terraform.tfvars.example create mode 100644 examples/fabric/v4/cloudRouterConnectivity/routing-protocols/variables.tf diff --git a/.gitignore b/.gitignore index c94dc28ab..fc9ce1e79 100644 --- a/.gitignore +++ b/.gitignore @@ -6,13 +6,11 @@ # Terraform specific files terraform.tfplan terraform.tfstate -*.terraform.lock.hcl -./*.tfstate .terraform/ example.tf terraform-provider-equinix -.terraform.lock.hcl *.tfvars +*.terraform* # Local Scripts bin/ diff --git a/docs/data-sources/equinix_fabric_routing_protocol.md b/docs/data-sources/equinix_fabric_routing_protocol.md index d32eb9bc4..504584914 100644 --- a/docs/data-sources/equinix_fabric_routing_protocol.md +++ b/docs/data-sources/equinix_fabric_routing_protocol.md @@ -3,13 +3,13 @@ page_title: "equinix_fabric_routing_protocol Data Source - terraform-provider-equinix" subcategory: "Fabric" description: |- - Fabric V4 API compatible data resource that allow user to fetch routing protocol for a given UUID + Fabric V4 API compatible data resource that allows user to fetch routing protocol for a given UUID ~> Note Equinix Fabric v4 resources and datasources are currently in Beta. The interfaces related to equinix_fabric_ resources and datasources may change ahead of general availability. Please, do not hesitate to report any problems that you experience by opening a new issue https://github.com/equinix/terraform-provider-equinix/issues/new?template=bug.md --- # equinix_fabric_routing_protocol (Data Source) -Fabric V4 API compatible data resource that allow user to fetch routing protocol for a given UUID +Fabric V4 API compatible data resource that allows user to fetch routing protocol for a given UUID ~> **Note** Equinix Fabric v4 resources and datasources are currently in Beta. The interfaces related to `equinix_fabric_` resources and datasources may change ahead of general availability. Please, do not hesitate to report any problems that you experience by opening a new [issue](https://github.com/equinix/terraform-provider-equinix/issues/new?template=bug.md) diff --git a/docs/data-sources/equinix_fabric_routing_protocols.md b/docs/data-sources/equinix_fabric_routing_protocols.md new file mode 100644 index 000000000..9c8325645 --- /dev/null +++ b/docs/data-sources/equinix_fabric_routing_protocols.md @@ -0,0 +1,205 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "equinix_fabric_routing_protocols Resource - terraform-provider-equinix" +subcategory: "Fabric" +description: |- +Fabric V4 API compatible data resource that allows user to fetch routing protocols for a given routing protocol ids +~> Note Equinix Fabric v4 resources and datasources are currently in Beta. The interfaces related to equinix_fabric_ resources and datasources may change ahead of general availability. Please, do not hesitate to report any problems that you experience by opening a new issue https://github.com/equinix/terraform-provider-equinix/issues/new?template=bug.md +--- + +# equinix_fabric_routing_protocols (Data Source) + +Fabric V4 API compatible data resource that allows user to fetch routing protocols for a given routing protocol ids + +~> **Note** Equinix Fabric v4 resources and datasources are currently in Beta. The interfaces related to `equinix_fabric_` resources and datasources may change ahead of general availability. Please, do not hesitate to report any problems that you experience by opening a new [issue](https://github.com/equinix/terraform-provider-equinix/issues/new?template=bug.md) + +API documentation can be found here - https://developer.equinix.com/dev-docs/fabric/api-reference/fabric-v4-apis#routing-protocols + + +## Example Usage + +```hcl +provider "equinix" { + client_id = "" + client_secret = "" +} + +data "equinix_fabric_routing_protocols" "read_protocols" { + connection_uuid = "d7ab9902-4d89-4fc0-9a5f-9faea39d82a3" + direct_routing_protocol_uuid = "6534071e-6216-4943-b8c4-1d2f2f0cbb07" + bgp_routing_protocol_uuid = "c1c052d0-8da9-4918-94fc-3e507f3ebf6a" +} +``` + + +## Schema + +### Required + +- `connection_uuid` (String) Connection URI associated with Routing Protocol +- `direct_routing_protocol_uuid` (String) Direct Routing Protocol URI +- `bgp_routing_protocol_uuid` (String) BGP Routing Protocol URI + +### Read-Only + +- `direct_routing_protocol` (Block Set) Direct Routing Protocol Details (see [below for nested schema](#nestedblock--direct)) +- `bgp_routing_protocol` (Block Set) BGP Routing Protocol Details (see [below for nested schema](#nestedblock--bgp)) +- `id` (String) The ID of this resource. + + +### Nested Schema for `direct-routing-protocol` + +Required: +- `name` (String) Routing Protocol name. An alpha-numeric 24 characters string which can include only hyphens and underscores +- `direct_ipv4` (Block Set) Routing Protocol Direct IPv4 (see [below for nested schema](#nestedblock--direct_ipv4)) + +Optional: +- `direct_ipv6` (Block Set) Routing Protocol Direct IPv6 (see [below for nested schema](#nestedblock--direct_ipv6)) + +Read-Only: +- `type` (String) Defines the routing protocol type like BGP or DIRECT +- `href` (String) Routing Protocol URI information +- `uuid` (String) Equinix-assigned routing protocol identifier +- `state` (String) Routing Protocol overall state +- `operation` (Set of Object) Routing Protocol type-specific operational data (see [below for nested schema](#nestedatt--operation)) +- `change` (Set of Object) Routing Protocol configuration Changes (see [below for nested schema](#nestedatt--change)) +- `change_log` (Set of Object) Captures Routing Protocol lifecycle change information (see [below for nested schema](#nestedatt--change_log)) + + +### Nested Schema for `bgp-routing-protocol` + +Optional: +- `name` (String) Routing Protocol name. An alpha-numeric 24 characters string which can include only hyphens and underscores +- `bgp_ipv4` (Block Set) Routing Protocol BGP IPv4 (see [below for nested schema](#nestedblock--bgp_ipv4)) +- `bgp_ipv6` (Block Set) Routing Protocol BGP IPv6 (see [below for nested schema](#nestedblock--bgp_ipv6)) +- `customer_asn` (Number) Customer-provided ASN +- `equinix_asn` (Number) Equinix ASN +- `bgp_auth_key` (String) BGP authorization key +- `bfd` (Block Set) Bidirectional Forwarding Detection (see [below for nested schema](#nestedblock--bfd)) + +Read-Only: +- `type` (String) Defines the routing protocol type like BGP or DIRECT +- `href` (String) Routing Protocol URI information +- `uuid` (String) Equinix-assigned routing protocol identifier +- `state` (String) Routing Protocol overall state +- `operation` (Set of Object) Routing Protocol type-specific operational data (see [below for nested schema](#nestedatt--operation)) +- `change` (Set of Object) Routing Protocol configuration Changes (see [below for nested schema](#nestedatt--change)) +- `change_log` (Set of Object) Captures Routing Protocol lifecycle change information (see [below for nested schema](#nestedatt--change_log)) + + +### Nested Schema for `bfd` + +Required: + +- `enabled` (Boolean) Bidirectional Forwarding Detection enablement + +Optional: + +- `interval` (String) Interval range between the received BFD control packets + + + +### Nested Schema for `bgp_ipv4` + +Required: + +- `customer_peer_ip` (String) Customer side peering ip + +Optional: + +- `enabled` (Boolean) Admin status for the BGP session + +Read-Only: + +- `equinix_peer_ip` (String) Equinix side peering ip + + + +### Nested Schema for `bgp_ipv6` + +Required: + +- `customer_peer_ip` (String) Customer side peering ip + +Optional: + +- `enabled` (Boolean) Admin status for the BGP session + +Read-Only: + +- `equinix_peer_ip` (String) Equinix side peering ip + + + +### Nested Schema for `direct_ipv4` + +Required: + +- `equinix_iface_ip` (String) Equinix side Interface IP address + + + +### Nested Schema for `direct_ipv6` + +Optional: + +- `equinix_iface_ip` (String) Equinix side Interface IP address + + + +### Nested Schema for `change` + +Read-Only: + +- `href` (String) +- `type` (String) +- `uuid` (String) + + + +### Nested Schema for `change_log` + +Read-Only: + +- `created_by` (String) +- `created_by_email` (String) +- `created_by_full_name` (String) +- `created_date_time` (String) +- `deleted_by` (String) +- `deleted_by_email` (String) +- `deleted_by_full_name` (String) +- `deleted_date_time` (String) +- `updated_by` (String) +- `updated_by_email` (String) +- `updated_by_full_name` (String) +- `updated_date_time` (String) + + + +### Nested Schema for `operation` + +Read-Only: + +- `errors` (List of Object) (see [below for nested schema](#nestedobjatt--operation--errors)) + + +### Nested Schema for `operation.errors` + +Read-Only: + +- `additional_info` (List of Object) (see [below for nested schema](#nestedobjatt--operation--errors--additional_info)) +- `correlation_id` (String) +- `details` (String) +- `error_code` (String) +- `error_message` (String) +- `help` (String) + + +### Nested Schema for `operation.errors.additional_info` + +Read-Only: + +- `property` (String) +- `reason` (String) + + diff --git a/docs/resources/equinix_fabric_routing_protocol.md b/docs/resources/equinix_fabric_routing_protocol.md index 2ac41cbfb..645ca9f0a 100644 --- a/docs/resources/equinix_fabric_routing_protocol.md +++ b/docs/resources/equinix_fabric_routing_protocol.md @@ -3,13 +3,13 @@ page_title: "equinix_fabric_routing_protocol Resource - terraform-provider-equinix" subcategory: "Fabric" description: |- - Fabric V4 API compatible resource allows creation and management of Equinix Fabric connection + Fabric V4 API compatible resource allows creation and management of Equinix Fabric routing protocol ~> Note Equinix Fabric v4 resources and datasources are currently in Beta. The interfaces related to equinix_fabric_ resources and datasources may change ahead of general availability. Please, do not hesitate to report any problems that you experience by opening a new issue https://github.com/equinix/terraform-provider-equinix/issues/new?template=bug.md --- # equinix_fabric_routing_protocol (Resource) -Fabric V4 API compatible resource allows creation and management of Equinix Fabric connection +Fabric V4 API compatible resource allows creation and management of Equinix Fabric routing protocol ~> **Note** Equinix Fabric v4 resources and datasources are currently in Beta. The interfaces related to `equinix_fabric_` resources and datasources may change ahead of general availability. Please, do not hesitate to report any problems that you experience by opening a new [issue](https://github.com/equinix/terraform-provider-equinix/issues/new?template=bug.md) diff --git a/docs/resources/equinix_fabric_routing_protocols.md b/docs/resources/equinix_fabric_routing_protocols.md new file mode 100644 index 000000000..dc7955299 --- /dev/null +++ b/docs/resources/equinix_fabric_routing_protocols.md @@ -0,0 +1,230 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "equinix_fabric_routing_protocols Resource - terraform-provider-equinix" +subcategory: "Fabric" +description: |- + Fabric V4 API compatible resource allows creation and management of Equinix Fabric routing protocols + ~> Note Equinix Fabric v4 resources and datasources are currently in Beta. The interfaces related to equinix_fabric_ resources and datasources may change ahead of general availability. Please, do not hesitate to report any problems that you experience by opening a new issue https://github.com/equinix/terraform-provider-equinix/issues/new?template=bug.md +--- + +# equinix_fabric_routing_protocols (Resource) + +Fabric V4 API compatible resource allows creation and management of Equinix Fabric routing protocols + +~> **Note** Equinix Fabric v4 resources and datasources are currently in Beta. The interfaces related to `equinix_fabric_` resources and datasources may change ahead of general availability. Please, do not hesitate to report any problems that you experience by opening a new [issue](https://github.com/equinix/terraform-provider-equinix/issues/new?template=bug.md) + +API documentation can be found here - https://developer.equinix.com/dev-docs/fabric/api-reference/fabric-v4-apis#routing-protocols + + +## Example Usage + + +Create Direct and BGP Routing Protocol on Some Connection UUID: +```hcl +provider "equinix" { + client_id = "" + client_secret = "" +} + +resource "equinix_fabric_routing_protocols" "routing_protocols" { + connection_uuid = "b0eb3892-6404-442b-85c0-4f8fcf53d123" + + direct_routing_protocol { + name = "FCR-Con-Direct-RP" + direct_ipv4 { + equinix_iface_ip = "190.1.1.1/30" + } + direct_ipv6 { + equinix_iface_ip = "190::1:1/126" + } + } + + bgp_routing_protocol { + name = "FCR-Con-BGP-RP" + bgp_ipv4 { + customer_peer_ip = "190.1.1.2" + enabled = true + } + bgp_ipv6 { + customer_peer_ip = "190::1:2" + enabled = true + } + customer_asn = "100" + equinix_asn = "1345" + } +} +``` + + +## Schema + +### Required + +- `connection_uuid` (String) Connection URI associated with Routing Protocol +- `direct_routing_protocol` (Block Set) Direct Routing Protocol Details (see [below for nested schema](#nestedblock--direct)) + +### Optional + +- `bgp_routing_protocol` (Block Set) BGP Routing Protocol Details (see [below for nested schema](#nestedblock--bgp)) + +### Read-Only + +- `id` (String) The ID of this resource. + + +### Nested Schema for `direct-routing-protocol` + +Required: +- `name` (String) Routing Protocol name. An alpha-numeric 24 characters string which can include only hyphens and underscores +- `direct_ipv4` (Block Set) Routing Protocol Direct IPv4 (see [below for nested schema](#nestedblock--direct_ipv4)) + +Optional: +- `direct_ipv6` (Block Set) Routing Protocol Direct IPv6 (see [below for nested schema](#nestedblock--direct_ipv6)) + +Read-Only: +- `type` (String) Defines the routing protocol type like BGP or DIRECT +- `href` (String) Routing Protocol URI information +- `uuid` (String) Equinix-assigned routing protocol identifier +- `state` (String) Routing Protocol overall state +- `operation` (Set of Object) Routing Protocol type-specific operational data (see [below for nested schema](#nestedatt--operation)) +- `change` (Set of Object) Routing Protocol configuration Changes (see [below for nested schema](#nestedatt--change)) +- `change_log` (Set of Object) Captures Routing Protocol lifecycle change information (see [below for nested schema](#nestedatt--change_log)) + + +### Nested Schema for `bgp-routing-protocol` + +Optional: +- `name` (String) Routing Protocol name. An alpha-numeric 24 characters string which can include only hyphens and underscores +- `bgp_ipv4` (Block Set) Routing Protocol BGP IPv4 (see [below for nested schema](#nestedblock--bgp_ipv4)) +- `bgp_ipv6` (Block Set) Routing Protocol BGP IPv6 (see [below for nested schema](#nestedblock--bgp_ipv6)) +- `customer_asn` (Number) Customer-provided ASN +- `equinix_asn` (Number) Equinix ASN +- `bgp_auth_key` (String) BGP authorization key +- `bfd` (Block Set) Bidirectional Forwarding Detection (see [below for nested schema](#nestedblock--bfd)) + +Read-Only: +- `type` (String) Defines the routing protocol type like BGP or DIRECT +- `href` (String) Routing Protocol URI information +- `uuid` (String) Equinix-assigned routing protocol identifier +- `state` (String) Routing Protocol overall state +- `operation` (Set of Object) Routing Protocol type-specific operational data (see [below for nested schema](#nestedatt--operation)) +- `change` (Set of Object) Routing Protocol configuration Changes (see [below for nested schema](#nestedatt--change)) +- `change_log` (Set of Object) Captures Routing Protocol lifecycle change information (see [below for nested schema](#nestedatt--change_log)) + + +### Nested Schema for `bfd` + +Required: + +- `enabled` (Boolean) Bidirectional Forwarding Detection enablement + +Optional: + +- `interval` (String) Interval range between the received BFD control packets + + + +### Nested Schema for `bgp_ipv4` + +Required: + +- `customer_peer_ip` (String) Customer side peering ip + +Optional: + +- `enabled` (Boolean) Admin status for the BGP session + +Read-Only: + +- `equinix_peer_ip` (String) Equinix side peering ip + + + +### Nested Schema for `bgp_ipv6` + +Required: + +- `customer_peer_ip` (String) Customer side peering ip + +Optional: + +- `enabled` (Boolean) Admin status for the BGP session + +Read-Only: + +- `equinix_peer_ip` (String) Equinix side peering ip + + + +### Nested Schema for `direct_ipv4` + +Required: + +- `equinix_iface_ip` (String) Equinix side Interface IP address + + + +### Nested Schema for `direct_ipv6` + +Optional: + +- `equinix_iface_ip` (String) Equinix side Interface IP address + + + +### Nested Schema for `change` + +Read-Only: + +- `href` (String) +- `type` (String) +- `uuid` (String) + + + +### Nested Schema for `change_log` + +Read-Only: + +- `created_by` (String) +- `created_by_email` (String) +- `created_by_full_name` (String) +- `created_date_time` (String) +- `deleted_by` (String) +- `deleted_by_email` (String) +- `deleted_by_full_name` (String) +- `deleted_date_time` (String) +- `updated_by` (String) +- `updated_by_email` (String) +- `updated_by_full_name` (String) +- `updated_date_time` (String) + + + +### Nested Schema for `operation` + +Read-Only: + +- `errors` (List of Object) (see [below for nested schema](#nestedobjatt--operation--errors)) + + +### Nested Schema for `operation.errors` + +Read-Only: + +- `additional_info` (List of Object) (see [below for nested schema](#nestedobjatt--operation--errors--additional_info)) +- `correlation_id` (String) +- `details` (String) +- `error_code` (String) +- `error_message` (String) +- `help` (String) + + +### Nested Schema for `operation.errors.additional_info` + +Read-Only: + +- `property` (String) +- `reason` (String) + + diff --git a/equinix/data_source_fabric_routing_protocols.go b/equinix/data_source_fabric_routing_protocols.go new file mode 100644 index 000000000..f61b68573 --- /dev/null +++ b/equinix/data_source_fabric_routing_protocols.go @@ -0,0 +1,64 @@ +package equinix + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func FabricRoutingProtocolsDataResourceSchema() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "connection_uuid": { + Type: schema.TypeString, + Required: true, + Description: "Connection URI associated with Routing Protocol", + }, + "direct_routing_protocol_uuid": { + Type: schema.TypeString, + Required: true, + Description: "Direct Routing Protcol UUID", + }, + "bgp_routing_protocol_uuid": { + Type: schema.TypeString, + Optional: true, + Description: "BGP Routing Protcol UUID", + }, + "direct_routing_protocol": { + Type: schema.TypeSet, + Computed: true, + Description: "Routing Protocol Direct Details", + Elem: &schema.Resource{ + Schema: DirectRoutingProtocolSch(), + }, + }, + "bgp_routing_protocol": { + Type: schema.TypeSet, + Computed: true, + Description: "BGP Routing Protocol Details", + Elem: &schema.Resource{ + Schema: BGPRoutingProtocolSch(), + }, + }, + } +} + +func dataSourceRoutingProtocols() *schema.Resource { + return &schema.Resource{ + ReadContext: dataSourceRoutingProtocolsRead, + Schema: FabricRoutingProtocolsDataResourceSchema(), + Description: "Fabric V4 API compatible data resource that allow user to fetch routing protocols for the given routing protocol ids\n\n~> **Note** Equinix Fabric v4 resources and datasources are currently in Beta. The interfaces related to `equinix_fabric_` resources and datasources may change ahead of general availability. Please, do not hesitate to report any problems that you experience by opening a new [issue](https://github.com/equinix/terraform-provider-equinix/issues/new?template=bug.md)", + } +} + +func dataSourceRoutingProtocolsRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + directUUID, _ := d.Get("direct_routing_protocol_uuid").(string) + bgpUUID, _ := d.Get("bgp_routing_protocol_uuid").(string) + id := directUUID + if bgpUUID != "" { + id = directUUID + "/" + bgpUUID + } + + d.SetId(id) + return resourceFabricRoutingProtocolsRead(ctx, d, meta) +} diff --git a/equinix/errors.go b/equinix/errors.go index 61c03c5a4..b20ba5517 100644 --- a/equinix/errors.go +++ b/equinix/errors.go @@ -98,7 +98,7 @@ func setMap(d *schema.ResourceData, m map[string]interface{}) error { err = f(d, key) } else { if key == "router" { - d.Set("gateway", v) + err = d.Set("gateway", v) } err = d.Set(key, v) } diff --git a/equinix/fabric_mapping_helper.go b/equinix/fabric_mapping_helper.go index 8f40dbceb..a27f70fd5 100644 --- a/equinix/fabric_mapping_helper.go +++ b/equinix/fabric_mapping_helper.go @@ -899,42 +899,92 @@ func routingProtocolBfdToFabric(routingProtocolBfdRequest []interface{}) v4.Rout return mappedRpBfd } -func routingProtocolChangeToFabric(routingProtocolChangeRequest []interface{}) v4.RoutingProtocolChange { - mappedRpChange := v4.RoutingProtocolChange{} - for _, str := range routingProtocolChangeRequest { - rpChangeMap := str.(map[string]interface{}) - uuid := rpChangeMap["uuid"].(string) - rpChangeType := rpChangeMap["type"].(string) - - mappedRpChange = v4.RoutingProtocolChange{Uuid: uuid, Type_: rpChangeType} - } - return mappedRpChange -} +func directRoutingProtocolToFabric(directRoutingProtocolConfig []interface{}) v4.RoutingProtocolDirectType { + mappedDirectRP := v4.RoutingProtocolDirectType{} + for _, rp := range directRoutingProtocolConfig { + rpMap := rp.(map[string]interface{}) + mappedDirectRP.Type_ = rpMap["type"].(string) + mappedDirectRP.Name = rpMap["name"].(string) + directIpv4 := routingProtocolDirectIpv4ToFabric(rpMap["direct_ipv4"].(*schema.Set).List()) + directIpv6 := routingProtocolDirectIpv6ToFabric(rpMap["direct_ipv6"].(*schema.Set).List()) + mappedDirectRP.DirectIpv4 = &directIpv4 + mappedDirectRP.DirectIpv6 = &directIpv6 + } + + return mappedDirectRP +} + +func bgpRoutingProtocolToFabric(bgpRoutingProtocolConfig []interface{}) v4.RoutingProtocolBgpType { + mappedBgpRp := v4.RoutingProtocolBgpType{} + for _, rp := range bgpRoutingProtocolConfig { + rpMap := rp.(map[string]interface{}) + mappedBgpRp.Type_ = rpMap["type"].(string) + mappedBgpRp.Name = rpMap["name"].(string) + bgpIpv4 := routingProtocolBgpIpv4ToFabric(rpMap["bgp_ipv4"].(*schema.Set).List()) + bgpIpv6 := routingProtocolBgpIpv6ToFabric(rpMap["bgp_ipv6"].(*schema.Set).List()) + mappedBgpRp.BgpIpv4 = &bgpIpv4 + mappedBgpRp.BgpIpv6 = &bgpIpv6 + mappedBgpRp.CustomerAsn = int64(rpMap["customer_asn"].(int)) + mappedBgpRp.EquinixAsn = int64(rpMap["equinix_asn"].(int)) + mappedBgpRp.BgpAuthKey = rpMap["bgp_auth_key"].(string) + bgpBfd := routingProtocolBfdToFabric(rpMap["bfd"].(*schema.Set).List()) + mappedBgpRp.Bfd = &bgpBfd + } + + return mappedBgpRp +} + +func directRoutingProtocolToTerra(directRoutingProtocol v4.RoutingProtocolDirectData) *schema.Set { + mappedDirectRoutingProtocol := map[string]interface{}{ + "type": directRoutingProtocol.Type_, + "name": directRoutingProtocol.Name, + "direct_ipv4": routingProtocolDirectConnectionIpv4ToTerra(directRoutingProtocol.DirectIpv4), + "direct_ipv6": routingProtocolDirectConnectionIpv6ToTerra(directRoutingProtocol.DirectIpv6), + "href": directRoutingProtocol.Href, + "uuid": directRoutingProtocol.Uuid, + "state": directRoutingProtocol.State, + "operation": routingProtocolOperationToTerra(directRoutingProtocol.Operation), + "change": routingProtocolChangeToTerra(directRoutingProtocol.Change), + "change_log": changeLogToTerra(directRoutingProtocol.Changelog), + } + + directRoutingProtocolSet := schema.NewSet( + schema.HashResource(&schema.Resource{ + Schema: DirectRoutingProtocolSch(), + }), + []interface{}{mappedDirectRoutingProtocol}, + ) -func routingProtocolDirectTypeToTerra(routingProtocolDirect *v4.RoutingProtocolDirectType) *schema.Set { - if routingProtocolDirect == nil { - return nil - } - routingProtocolDirects := []*v4.RoutingProtocolDirectType{routingProtocolDirect} - mappedDirects := make([]interface{}, len(routingProtocolDirects)) - for _, routingProtocolDirect := range routingProtocolDirects { - mappedDirect := make(map[string]interface{}) - mappedDirect["type"] = routingProtocolDirect.Type_ - mappedDirect["name"] = routingProtocolDirect.Name - if routingProtocolDirect.DirectIpv4 != nil { - mappedDirect["direct_ipv4"] = routingProtocolDirectConnectionIpv4ToTerra(routingProtocolDirect.DirectIpv4) - } - if routingProtocolDirect.DirectIpv6 != nil { - mappedDirect["direct_ipv6"] = routingProtocolDirectConnectionIpv6ToTerra(routingProtocolDirect.DirectIpv6) - } - mappedDirects = append(mappedDirects, mappedDirect) - } - rpDirectSet := schema.NewSet( - schema.HashResource(createRoutingProtocolDirectTypeRes), - mappedDirects, + return directRoutingProtocolSet +} + +func bgpRoutingProtocolToTerra(bgpRoutingProtocol v4.RoutingProtocolBgpData) *schema.Set { + + mappedBGPRoutingProtocol := map[string]interface{}{ + "type": bgpRoutingProtocol.Type_, + "name": bgpRoutingProtocol.Name, + "bgp_ipv4": routingProtocolBgpConnectionIpv4ToTerra(bgpRoutingProtocol.BgpIpv4), + "bgp_ipv6": routingProtocolBgpConnectionIpv6ToTerra(bgpRoutingProtocol.BgpIpv6), + "customer_asn": int(bgpRoutingProtocol.CustomerAsn), + "equinix_asn": int(bgpRoutingProtocol.EquinixAsn), + "bgp_auth_key": bgpRoutingProtocol.BgpAuthKey, + "bfd": routingProtocolBfdToTerra(bgpRoutingProtocol.Bfd), + "href": bgpRoutingProtocol.Href, + "uuid": bgpRoutingProtocol.Uuid, + "state": bgpRoutingProtocol.State, + "operation": routingProtocolOperationToTerra(bgpRoutingProtocol.Operation), + "change": routingProtocolChangeToTerra(bgpRoutingProtocol.Change), + "change_log": changeLogToTerra(bgpRoutingProtocol.Changelog), + } + + bgpRoutingProtocolSet := schema.NewSet( + schema.HashResource(&schema.Resource{ + Schema: BGPRoutingProtocolSch(), + }), + []interface{}{mappedBGPRoutingProtocol}, ) - return rpDirectSet + return bgpRoutingProtocolSet } func routingProtocolDirectConnectionIpv4ToTerra(routingProtocolDirectIpv4 *v4.DirectConnectionIpv4) *schema.Set { @@ -973,39 +1023,6 @@ func routingProtocolDirectConnectionIpv6ToTerra(routingProtocolDirectIpv6 *v4.Di return rpDirectIpv6Set } -func routingProtocolBgpTypeToTerra(routingProtocolBgp *v4.RoutingProtocolBgpType) *schema.Set { - if routingProtocolBgp == nil { - return nil - } - routingProtocolBgps := []*v4.RoutingProtocolBgpType{routingProtocolBgp} - mappedBgps := make([]interface{}, len(routingProtocolBgps)) - for _, routingProtocolBgp := range routingProtocolBgps { - mappedBgp := make(map[string]interface{}) - mappedBgp["type"] = routingProtocolBgp.Type_ - mappedBgp["name"] = routingProtocolBgp.Name - if routingProtocolBgp.BgpIpv4 != nil { - mappedBgp["bgp_ipv4"] = routingProtocolBgpConnectionIpv4ToTerra(routingProtocolBgp.BgpIpv4) - } - if routingProtocolBgp.BgpIpv6 != nil { - mappedBgp["bgp_ipv6"] = routingProtocolBgpConnectionIpv6ToTerra(routingProtocolBgp.BgpIpv6) - } - mappedBgp["customer_asn"] = routingProtocolBgp.CustomerAsn - mappedBgp["bgp_auth_key"] = routingProtocolBgp.BgpAuthKey - if routingProtocolBgp.Bfd != nil { - mappedBgp["bfd"] = routingProtocolBfdToTerra(routingProtocolBgp.Bfd) - } - - mappedBgps = append(mappedBgps, mappedBgp) - - } - rpBgpSet := schema.NewSet( - schema.HashResource(createRoutingProtocolBgpTypeRes), - mappedBgps, - ) - - return rpBgpSet -} - func routingProtocolBgpConnectionIpv4ToTerra(routingProtocolBgpIpv4 *v4.BgpConnectionIpv4) *schema.Set { if routingProtocolBgpIpv4 == nil { return nil @@ -1105,26 +1122,6 @@ func routingProtocolChangeToTerra(routingProtocolChange *v4.RoutingProtocolChang return rpChangeSet } -func getRoutingProtocolPatchUpdateRequest(rp v4.RoutingProtocolData, d *schema.ResourceData) (v4.ConnectionChangeOperation, error) { - changeOps := v4.ConnectionChangeOperation{} - existingBgpIpv4Status := rp.BgpIpv4.Enabled - existingBgpIpv6Status := rp.BgpIpv6.Enabled - updateBgpIpv4Status := d.Get("rp.BgpIpv4.Enabled") - updateBgpIpv6Status := d.Get("rp.BgpIpv6.Enabled") - - log.Printf("existing BGP IPv4 Status: %t, existing BGP IPv6 Status: %t, Update BGP IPv4 Request: %t, Update BGP Ipv6 Request: %t", - existingBgpIpv4Status, existingBgpIpv6Status, updateBgpIpv4Status, updateBgpIpv6Status) - - if existingBgpIpv4Status != updateBgpIpv4Status { - changeOps = v4.ConnectionChangeOperation{Op: "replace", Path: "/bgpIpv4/enabled", Value: updateBgpIpv4Status} - } else if existingBgpIpv6Status != updateBgpIpv6Status { - changeOps = v4.ConnectionChangeOperation{Op: "replace", Path: "/bgpIpv6/enabled", Value: updateBgpIpv6Status} - } else { - return changeOps, fmt.Errorf("nothing to update for the routing protocol %s", rp.RoutingProtocolBgpData.Uuid) - } - return changeOps, nil -} - func getUpdateRequests(conn v4.Connection, d *schema.ResourceData) ([][]v4.ConnectionChangeOperation, error) { var changeOps [][]v4.ConnectionChangeOperation existingName := conn.Name diff --git a/equinix/fabric_routing_protocols_schema.go b/equinix/fabric_routing_protocols_schema.go new file mode 100644 index 000000000..17cdd4def --- /dev/null +++ b/equinix/fabric_routing_protocols_schema.go @@ -0,0 +1,311 @@ +package equinix + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func FabricRoutingProtocolsResourceSchema() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "connection_uuid": { + Type: schema.TypeString, + Required: true, + Description: "Connection URI associated with Routing Protocol", + }, + "direct_routing_protocol": { + Type: schema.TypeSet, + Required: true, + Description: "Routing Protocol Direct Details", + Elem: &schema.Resource{ + Schema: DirectRoutingProtocolSch(), + }, + }, + "bgp_routing_protocol": { + Type: schema.TypeSet, + Optional: true, + Description: "BGP Routing Protocol Details", + Elem: &schema.Resource{ + Schema: BGPRoutingProtocolSch(), + }, + }, + } +} + +func DirectRoutingProtocolSch() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "type": { + Type: schema.TypeString, + Computed: true, + Description: "Defines the routing protocol type as DIRECT", + }, + "name": { + Type: schema.TypeString, + Optional: true, + Description: "Routing Protocol name. An alpha-numeric 24 characters string which can include only hyphens and underscores.", + }, + "direct_ipv4": { + Type: schema.TypeSet, + Required: true, + Description: "Direct Routing Protocol IPv4", + Elem: &schema.Resource{ + Schema: createDirectIpv4Sch(), + }, + }, + "direct_ipv6": { + Type: schema.TypeSet, + Optional: true, + Description: "Direct Routing Protocol IPv6", + Elem: &schema.Resource{ + Schema: createDirectIpv6Sch(), + }, + }, + "href": { + Type: schema.TypeString, + Computed: true, + Description: "Routing Protocol URI information", + }, + "uuid": { + Type: schema.TypeString, + Computed: true, + Description: "Equinix-assigned routing protocol identifier", + }, + "state": { + Type: schema.TypeString, + Computed: true, + Description: "Routing Protocol overall state", + }, + "operation": { + Type: schema.TypeSet, + Computed: true, + Description: "Routing Protocol type-specific operational data", + Elem: &schema.Resource{ + Schema: routingProtocolOperationSch(), + }, + }, + "change": { + Type: schema.TypeSet, + Computed: true, + Description: "Routing Protocol configuration Changes", + Elem: &schema.Resource{ + Schema: routingProtocolChangeSch(), + }, + }, + "change_log": { + Type: schema.TypeSet, + Computed: true, + Description: "Captures Routing Protocol lifecycle change information", + Elem: &schema.Resource{ + Schema: createChangeLogSch(), + }, + }, + } +} + +func createDirectIpv4Sch() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "equinix_iface_ip": { + Type: schema.TypeString, + Required: true, + Description: "Equinix side Interface IP address", + }, + } +} + +func createDirectIpv6Sch() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "equinix_iface_ip": { + Type: schema.TypeString, + Required: true, + Description: "Equinix side Interface IP address\n\n", + }, + } +} + +func BGPRoutingProtocolSch() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "type": { + Type: schema.TypeString, + Computed: true, + Description: "Defines the routing protocol type as DIRECT", + }, + "name": { + Type: schema.TypeString, + Optional: true, + Description: "Routing Protocol name. An alpha-numeric 24 characters string which can include only hyphens and underscores.", + }, + "bgp_ipv4": { + Type: schema.TypeSet, + Optional: true, + Description: "Routing Protocol BGP IPv4", + Elem: &schema.Resource{ + Schema: createBgpIpv4Sch(), + }, + }, + "bgp_ipv6": { + Type: schema.TypeSet, + Optional: true, + Description: "Routing Protocol BGP IPv6", + Elem: &schema.Resource{ + Schema: createBgpIpv6Sch(), + }, + }, + "customer_asn": { + Type: schema.TypeInt, + Optional: true, + Description: "Customer-provided ASN", + }, + "equinix_asn": { + Type: schema.TypeInt, + Optional: true, + Description: "Equinix ASN", + }, + "bgp_auth_key": { + Type: schema.TypeString, + Optional: true, + Description: "BGP authorization key", + }, + "bfd": { + Type: schema.TypeSet, + Optional: true, + Description: "Bidirectional Forwarding Detection", + Elem: &schema.Resource{ + Schema: createRoutingProtocolsBfdSch(), + }, + }, + "href": { + Type: schema.TypeString, + Computed: true, + Description: "Routing Protocol URI information", + }, + "uuid": { + Type: schema.TypeString, + Computed: true, + Description: "Equinix-assigned routing protocol identifier", + }, + "state": { + Type: schema.TypeString, + Computed: true, + Description: "Routing Protocol overall state", + }, + "operation": { + Type: schema.TypeSet, + Computed: true, + Description: "Routing Protocol type-specific operational data", + Elem: &schema.Resource{ + Schema: routingProtocolOperationSch(), + }, + }, + "change": { + Type: schema.TypeSet, + Computed: true, + Description: "Routing Protocol configuration Changes", + Elem: &schema.Resource{ + Schema: routingProtocolChangeSch(), + }, + }, + "change_log": { + Type: schema.TypeSet, + Computed: true, + Description: "Captures Routing Protocol lifecycle change information", + Elem: &schema.Resource{ + Schema: createChangeLogSch(), + }, + }, + } +} + +func createBgpIpv4Sch() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "customer_peer_ip": { + Type: schema.TypeString, + Optional: true, + Description: "Customer side peering ip", + }, + "equinix_peer_ip": { + Type: schema.TypeString, + Optional: true, + Description: "Equinix side peering ip", + }, + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + Description: "Admin status for the BGP session", + }, + } +} + +func createBgpIpv6Sch() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "customer_peer_ip": { + Type: schema.TypeString, + Required: true, + Description: "Customer side peering ip", + }, + "equinix_peer_ip": { + Type: schema.TypeString, + Optional: true, + Description: "Equinix side peering ip", + }, + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + Description: "Admin status for the BGP session", + }, + } +} + +func createRoutingProtocolsBfdSch() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "enabled": { + Type: schema.TypeBool, + Required: true, + Description: "Bidirectional Forwarding Detection enablement", + }, + "interval": { + Type: schema.TypeString, + Optional: true, + Default: 100, + Description: "Interval range between the received BFD control packets", + }, + } +} + +func routingProtocolOperationSch() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "errors": { + Type: schema.TypeList, + Computed: true, + Description: "Errors occurred", + Elem: &schema.Resource{ + Schema: createOperationalErrorSch(), + }, + }, + } +} + +func routingProtocolChangeSch() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "description": { + Type: schema.TypeString, + Computed: true, + Description: "Details of latest Routing Protocol change", + }, + "uuid": { + Type: schema.TypeString, + Computed: true, + Description: "Uniquely identifies a change", + }, + "type": { + Type: schema.TypeString, + Computed: true, + //ValidateFunc: validation.StringInSlice([]string{"ROUTING_PROTOCOL_UPDATE", "ROUTING_PROTOCOL_CREATION", "ROUTING_PROTOCOL_DELETION"}, true), + Description: "Type of change", + }, + "href": { + Type: schema.TypeString, + Computed: true, + Description: "Routing Protocol Change URI", + }, + } +} diff --git a/equinix/provider.go b/equinix/provider.go index d8eda438f..56e12c502 100644 --- a/equinix/provider.go +++ b/equinix/provider.go @@ -107,6 +107,7 @@ func Provider() *schema.Provider { "equinix_ecx_l2_sellerprofile": dataSourceECXL2SellerProfile(), "equinix_ecx_l2_sellerprofiles": dataSourceECXL2SellerProfiles(), "equinix_fabric_routing_protocol": dataSourceRoutingProtocol(), + "equinix_fabric_routing_protocols": dataSourceRoutingProtocols(), "equinix_fabric_connection": dataSourceFabricConnection(), "equinix_fabric_cloud_router": dataSourceCloudRouter(), "equinix_fabric_port": dataSourceFabricPort(), @@ -148,6 +149,7 @@ func Provider() *schema.Provider { "equinix_fabric_cloud_router": resourceCloudRouter(), "equinix_fabric_connection": resourceFabricConnection(), "equinix_fabric_routing_protocol": resourceFabricRoutingProtocol(), + "equinix_fabric_routing_protocols": resourceFabricRoutingProtocols(), "equinix_fabric_service_profile": resourceFabricServiceProfile(), "equinix_network_device": resourceNetworkDevice(), "equinix_network_ssh_user": resourceNetworkSSHUser(), diff --git a/equinix/resource_fabric_routing_protocol.go b/equinix/resource_fabric_routing_protocol.go index 92d04c855..41af8eba8 100644 --- a/equinix/resource_fabric_routing_protocol.go +++ b/equinix/resource_fabric_routing_protocol.go @@ -49,7 +49,7 @@ func resourceFabricRoutingProtocol() *schema.Resource { func resourceFabricRoutingProtocolRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*Config).fabricClient ctx = context.WithValue(ctx, v4.ContextAccessToken, meta.(*Config).FabricAuthToken) - log.Printf("[WARN] Routing Protocol Connection uuid: %s", d.Get("connection_uuid").(string)) + log.Printf("[INFO] Routing Protocol Connection uuid: %s", d.Get("connection_uuid").(string)) fabricRoutingProtocol, _, err := client.RoutingProtocolsApi.GetConnectionRoutingProtocolByUuid(ctx, d.Id(), d.Get("connection_uuid").(string)) if err != nil { log.Printf("[WARN] Routing Protocol %s not found , error %s", d.Id(), err) @@ -155,12 +155,6 @@ func resourceFabricRoutingProtocolUpdate(ctx context.Context, d *schema.Resource client := meta.(*Config).fabricClient ctx = context.WithValue(ctx, v4.ContextAccessToken, meta.(*Config).FabricAuthToken) - /* todo: support patch bgp in the future - switch between PUT and PATCH - 1. get getRoutingProtocolPatchUpdateRequest() - 2. call PatchConnectionRoutingProtocolByUuid() with id and connection_uuid - 3. waitForRoutingProtocolUpdateCompletion() with change_uuid, id, and connection_uuid - */ - schemaBgpIpv4 := d.Get("bgp_ipv4").(*schema.Set).List() bgpIpv4 := routingProtocolBgpIpv4ToFabric(schemaBgpIpv4) schemaBgpIpv6 := d.Get("bgp_ipv6").(*schema.Set).List() diff --git a/equinix/resource_fabric_routing_protocols.go b/equinix/resource_fabric_routing_protocols.go new file mode 100644 index 000000000..3779f5199 --- /dev/null +++ b/equinix/resource_fabric_routing_protocols.go @@ -0,0 +1,498 @@ +package equinix + +import ( + "context" + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "log" + "strconv" + "strings" + "time" + + v4 "github.com/equinix-labs/fabric-go/fabric/v4" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceFabricRoutingProtocols() *schema.Resource { + return &schema.Resource{ + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(6 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(6 * time.Minute), + Read: schema.DefaultTimeout(6 * time.Minute), + }, + ReadContext: resourceFabricRoutingProtocolsRead, + CreateContext: resourceFabricRoutingProtocolsCreate, + UpdateContext: resourceFabricRoutingProtocolsUpdate, + DeleteContext: resourceFabricRoutingProtocolsDelete, + Importer: &schema.ResourceImporter{ + // Custom state context function, to parse import argument as connection_uuid/direct-rp-uuid/bgp-rp-uuid + StateContext: func(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + parts := strings.SplitN(d.Id(), "/", 3) + if len(parts) < 2 || len(parts) > 3 || parts[0] == "" || parts[1] == "" { + return nil, fmt.Errorf("unexpected format of ID (%s), expected / or //", d.Id()) + } + connectionUuid, directRPUUID := parts[0], parts[1] + idToSet := directRPUUID + _ = d.Set("connection_uuid", connectionUuid) + if len(parts) == 3 && parts[2] == "" { + return nil, fmt.Errorf("unexpected format of ID (%s), expected / or //", d.Id()) + } else if len(parts) == 3 { + bgpRPUUID := parts[2] + idToSet += "/" + bgpRPUUID + } + + d.SetId(idToSet) + return []*schema.ResourceData{d}, nil + }, + }, + Schema: FabricRoutingProtocolsResourceSchema(), + + Description: "Fabric V4 API compatible resource allows creation and management of Equinix Fabric connection\n\n~> **Note** Equinix Fabric v4 resources and datasources are currently in Beta. The interfaces related to `equinix_fabric_` resources and datasources may change ahead of general availability. Please, do not hesitate to report any problems that you experience by opening a new [issue](https://github.com/equinix/terraform-provider-equinix/issues/new?template=bug.md)", + } +} + +type RoutingProtocols struct { + connectionUUID string + directRoutingProtocol v4.RoutingProtocolDirectData + bgpRoutingProtocol v4.RoutingProtocolBgpData +} + +func setFabricRoutingProtocolsMap(d *schema.ResourceData, rp RoutingProtocols) diag.Diagnostics { + diags := diag.Diagnostics{} + + err := error(nil) + + err = d.Set("connection_uuid", rp.connectionUUID) + + if err != nil { + return diag.FromErr(err) + } + + if rp.directRoutingProtocol != (v4.RoutingProtocolDirectData{}) { + err = setMap(d, map[string]interface{}{ + "direct_routing_protocol": directRoutingProtocolToTerra(rp.directRoutingProtocol), + }) + } + + if err != nil { + return diag.FromErr(err) + } + + if rp.bgpRoutingProtocol != (v4.RoutingProtocolBgpData{}) { + err = setMap(d, map[string]interface{}{ + "bgp_routing_protocol": bgpRoutingProtocolToTerra(rp.bgpRoutingProtocol), + }) + } + if err != nil { + return diag.FromErr(err) + } + + return diags +} + +func readUUIDsFromId(id string) (directRPUUID, bgpRPUUID string) { + ids := strings.SplitN(id, "/", 2) + if len(ids) == 2 && ids[1] != "" { + return ids[0], ids[1] + } + return ids[0], "" +} + +func readIdsFromBulkCreateResponse(bulkCreate v4.GetResponse) string { + directRPUUID, bgpRPUUID := "", "" + for _, rp := range bulkCreate.Data { + if rp.Type_ == "DIRECT" { + directRPUUID = rp.RoutingProtocolDirectData.Uuid + } + if rp.Type_ == "BGP" { + bgpRPUUID = rp.RoutingProtocolBgpData.Uuid + } + } + + return directRPUUID + "/" + bgpRPUUID +} + +func resourceFabricRoutingProtocolsRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*Config).fabricClient + ctx = context.WithValue(ctx, v4.ContextAccessToken, meta.(*Config).FabricAuthToken) + log.Printf("[INFO] Routing Protocols Resource Id: %s", d.Id()) + directRPUUID, bgpRPUUID := readUUIDsFromId(d.Id()) + connectionUUID := d.Get("connection_uuid").(string) + log.Printf("[INFO] Routing Protocol Direct uuid: %s", directRPUUID) + log.Printf("[INFO] Routing Protocol Direct uuid: %s", bgpRPUUID) + log.Printf("[INFO] Routing Protocol Connection uuid: %s", connectionUUID) + + directRoutingProtocol, _, err := client.RoutingProtocolsApi.GetConnectionRoutingProtocolByUuid(ctx, directRPUUID, connectionUUID) + if err != nil { + log.Printf("[WARN] Routing Protocol %s not found , error %s", directRPUUID, err) + if !strings.Contains(err.Error(), "500") { + d.SetId("") + } + return diag.FromErr(err) + } + + bgpRoutingProtocol := v4.RoutingProtocolBgpData{} + + if bgpRPUUID != "" { + bgpRoutingProtocolResponse, _, err := client.RoutingProtocolsApi.GetConnectionRoutingProtocolByUuid(ctx, bgpRPUUID, connectionUUID) + bgpRoutingProtocol = bgpRoutingProtocolResponse.RoutingProtocolBgpData + if err != nil { + log.Printf("[ERROR] Routing Protocol %s not found. Error: %s", bgpRPUUID, err) + if !strings.Contains(err.Error(), "500") { + d.SetId("") + } + return diag.FromErr(err) + } + } + + routingProtocols := RoutingProtocols{ + connectionUUID: connectionUUID, + directRoutingProtocol: directRoutingProtocol.RoutingProtocolDirectData, + bgpRoutingProtocol: bgpRoutingProtocol, + } + + return setFabricRoutingProtocolsMap(d, routingProtocols) +} + +func resourceFabricRoutingProtocolsCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*Config).fabricClient + ctx = context.WithValue(ctx, v4.ContextAccessToken, meta.(*Config).FabricAuthToken) + + connectionUUID := d.Get("connection_uuid").(string) + directRoutingProtocolConfig := d.Get("direct_routing_protocol").(*schema.Set).List() + bgpRoutingProtocolConfig := d.Get("bgp_routing_protocol").(*schema.Set).List() + directRoutingProtocol := directRoutingProtocolToFabric(directRoutingProtocolConfig) + bgpRoutingProtocol := bgpRoutingProtocolToFabric(bgpRoutingProtocolConfig) + + directRoutingProtocolRequest := v4.RoutingProtocolBase{ + Type_: "DIRECT", + OneOfRoutingProtocolBase: v4.OneOfRoutingProtocolBase{ + RoutingProtocolDirectType: directRoutingProtocol, + }, + } + + if bgpRoutingProtocol != (v4.RoutingProtocolBgpType{}) { + bgpRoutingProtocolRequest := v4.RoutingProtocolBase{ + Type_: "BGP", + OneOfRoutingProtocolBase: v4.OneOfRoutingProtocolBase{ + RoutingProtocolBgpType: bgpRoutingProtocol, + }, + } + + bulkRequest := v4.ConnectionRoutingProtocolPostRequest{ + Data: []v4.RoutingProtocolBase{ + directRoutingProtocolRequest, + bgpRoutingProtocolRequest, + }, + } + + bulkRPCreateResponse, _, err := client.RoutingProtocolsApi.CreateConnectionRoutingProtocolsInBulk(ctx, bulkRequest, connectionUUID) + if err != nil { + return diag.FromErr(err) + } + id := readIdsFromBulkCreateResponse(bulkRPCreateResponse) + log.Printf("[DEBUG] ids from bulk create: %s", id) + d.SetId(id) + directRPUUID, bgpRPUUID := readUUIDsFromId(id) + if _, err = waitUntilRoutingProtocolIsProvisioned(directRPUUID, connectionUUID, meta, ctx); err != nil { + return diag.Errorf("error waiting for Routing Protocol %s to be created: %s", d.Id(), err) + } + if _, err = waitUntilRoutingProtocolIsProvisioned(bgpRPUUID, connectionUUID, meta, ctx); err != nil { + return diag.Errorf("error waiting for Routing Protocol %s to be created: %s", d.Id(), err) + } + } else { + fabricRoutingProtocol, _, err := client.RoutingProtocolsApi.CreateConnectionRoutingProtocol(ctx, directRoutingProtocolRequest, connectionUUID) + if err != nil { + return diag.FromErr(err) + } + d.SetId(fabricRoutingProtocol.RoutingProtocolDirectData.Uuid) + + if _, err = waitUntilRoutingProtocolIsProvisioned(fabricRoutingProtocol.RoutingProtocolDirectData.Uuid, connectionUUID, meta, ctx); err != nil { + return diag.Errorf("error waiting for Routing Protocol %s to be created: %s", d.Id(), err) + } + } + + return resourceFabricRoutingProtocolsRead(ctx, d, meta) +} + +func directConfigHasChanged(old, new v4.RoutingProtocolDirectType) bool { + if (old.DirectIpv4 != nil && new.DirectIpv4 != nil && old.DirectIpv4.EquinixIfaceIp != new.DirectIpv4.EquinixIfaceIp) || + (old.DirectIpv4 != nil && new.DirectIpv4 != nil && old.DirectIpv6.EquinixIfaceIp != new.DirectIpv6.EquinixIfaceIp) { + return true + } + + return false +} + +func bgpConfigHasChanged(old, new v4.RoutingProtocolBgpType) bool { + if (old.BgpIpv4 != nil && new.BgpIpv4 != nil && old.BgpIpv4.CustomerPeerIp != new.BgpIpv4.CustomerPeerIp) || + (old.BgpIpv4 != nil && new.BgpIpv4 != nil && old.BgpIpv4.EquinixPeerIp != new.BgpIpv4.EquinixPeerIp) || + (old.BgpIpv4 != nil && new.BgpIpv4 != nil && old.BgpIpv4.Enabled != new.BgpIpv4.Enabled) || + (old.BgpIpv6 != nil && new.BgpIpv6 != nil && old.BgpIpv6.CustomerPeerIp != new.BgpIpv6.CustomerPeerIp) || + (old.BgpIpv6 != nil && new.BgpIpv6 != nil && old.BgpIpv6.EquinixPeerIp != new.BgpIpv6.EquinixPeerIp) || + (old.BgpIpv6 != nil && new.BgpIpv6 != nil && old.BgpIpv6.Enabled != new.BgpIpv6.Enabled) || + old.CustomerAsn != new.CustomerAsn || + old.EquinixAsn != new.CustomerAsn || + old.BgpAuthKey != new.BgpAuthKey || + (old.Bfd != nil && new.Bfd != nil && old.Bfd.Enabled != new.Bfd.Enabled) || + (old.Bfd != nil && new.Bfd != nil && old.Bfd.Interval != new.Bfd.Interval) { + return true + } + + return false +} + +func resourceFabricRoutingProtocolsUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*Config).fabricClient + ctx = context.WithValue(ctx, v4.ContextAccessToken, meta.(*Config).FabricAuthToken) + + directRPUUID, bgpRPUUID := readUUIDsFromId(d.Id()) + + connectionUUID := d.Get("connection_uuid").(string) + oldDirectRoutingProtocolConfig, newDirectRoutingProtocolConfig := d.GetChange("direct_routing_protocol") + oldBgpRoutingProtocolConfig, newBgpRoutingProtocolConfig := d.GetChange("bgp_routing_protocol") + + oldDirectRoutingProtocol, directRoutingProtocol := directRoutingProtocolToFabric(oldDirectRoutingProtocolConfig.(*schema.Set).List()), directRoutingProtocolToFabric(newDirectRoutingProtocolConfig.(*schema.Set).List()) + oldBgpRoutingProtocol, bgpRoutingProtocol := bgpRoutingProtocolToFabric(oldBgpRoutingProtocolConfig.(*schema.Set).List()), bgpRoutingProtocolToFabric(newBgpRoutingProtocolConfig.(*schema.Set).List()) + + directConfigChanged := directConfigHasChanged(oldDirectRoutingProtocol, directRoutingProtocol) + bgpConfigChanged := bgpConfigHasChanged(oldBgpRoutingProtocol, bgpRoutingProtocol) + + if !directConfigChanged && !bgpConfigChanged { + return diag.Diagnostics{} + } + + updatedDirectRP := v4.RoutingProtocolDirectData{} + + if directConfigChanged { + directRoutingProtocolRequest := v4.RoutingProtocolBase{ + Type_: "DIRECT", + OneOfRoutingProtocolBase: v4.OneOfRoutingProtocolBase{ + RoutingProtocolDirectType: directRoutingProtocol, + }, + } + + updateDirectRP, _, err := client.RoutingProtocolsApi.ReplaceConnectionRoutingProtocolByUuid(ctx, directRoutingProtocolRequest, directRPUUID, connectionUUID) + if err != nil { + return diag.FromErr(fmt.Errorf("error response for the routing protocol (%s) update: %v", directRPUUID, err)) + } + + _, err = waitForRoutingProtocolsUpdateCompletion(updateDirectRP.RoutingProtocolDirectData.Change.Uuid, directRPUUID, connectionUUID, meta, ctx) + if err != nil { + return diag.FromErr(fmt.Errorf("errored while waiting for successful routing protocol (%s) update: %v", directRPUUID, err)) + } + + d.SetId(updateDirectRP.RoutingProtocolDirectData.Uuid) + directRPUpdateProvisioned, err := waitUntilRoutingProtocolsIsProvisioned(directRPUUID, connectionUUID, meta, ctx) + updatedDirectRP = directRPUpdateProvisioned.RoutingProtocolDirectData + if err != nil { + return diag.Errorf("error waiting for Routing Protocol (%s) to be update to be in provisioned state: %s", directRPUUID, err) + } + } + + updatedBgpRP := v4.RoutingProtocolBgpData{} + + if bgpConfigChanged { + bgpRoutingProtocolRequest := v4.RoutingProtocolBase{ + Type_: "BGP", + OneOfRoutingProtocolBase: v4.OneOfRoutingProtocolBase{ + RoutingProtocolBgpType: bgpRoutingProtocol, + }, + } + + if bgpRPUUID != "" && bgpRoutingProtocol == (v4.RoutingProtocolBgpType{}) { + // BGP removed after creation, needs to be deleted + _, _, err := client.RoutingProtocolsApi.DeleteConnectionRoutingProtocolByUuid(ctx, bgpRPUUID, connectionUUID) + if err != nil { + return diag.FromErr(fmt.Errorf("error response for the routing protocol (%s) delete in update: %v", bgpRPUUID, err)) + } + + d.SetId(directRPUUID) + + err = waitUntilRoutingProtocolsIsDeprovisioned(bgpRPUUID, connectionUUID, meta, ctx) + if err != nil { + return diag.FromErr(fmt.Errorf("error waiting for routing protocol (%s) deletion in update: %v", bgpRPUUID, err)) + } + } else { + if bgpRPUUID != "" && bgpRoutingProtocol != (v4.RoutingProtocolBgpType{}) { + // BGP still here, needs to be updated + bgpRPUpdate, _, err := client.RoutingProtocolsApi.ReplaceConnectionRoutingProtocolByUuid(ctx, bgpRoutingProtocolRequest, bgpRPUUID, connectionUUID) + if err != nil { + return diag.FromErr(fmt.Errorf("error response for the routing protocol (%s) replace update: %v", directRPUUID, err)) + } + + _, err = waitForRoutingProtocolsUpdateCompletion(bgpRPUpdate.RoutingProtocolBgpData.Change.Uuid, bgpRPUUID, connectionUUID, meta, ctx) + if err != nil { + return diag.FromErr(fmt.Errorf("errored while waiting for successful routing protocol (%s) replace update: %v", bgpRPUUID, err)) + } + + d.SetId(directRPUUID + "/" + bgpRPUpdate.RoutingProtocolBgpData.Uuid) + + bgpRPUpdateProvisioned, err := waitUntilRoutingProtocolsIsProvisioned(bgpRPUUID, connectionUUID, meta, ctx) + if err != nil { + return diag.Errorf("error waiting for Routing Protocol (%s) to be replace updated: %s", bgpRPUUID, err) + } + updatedBgpRP = bgpRPUpdateProvisioned.RoutingProtocolBgpData + } else if bgpRPUUID == "" && bgpRoutingProtocol != (v4.RoutingProtocolBgpType{}) { + // BGP added after creation, needs to be created + bgpRPCreate, _, err := client.RoutingProtocolsApi.CreateConnectionRoutingProtocol(ctx, bgpRoutingProtocolRequest, connectionUUID) + if err != nil { + return diag.FromErr(err) + } + d.SetId(directRPUUID + "/" + bgpRPCreate.RoutingProtocolBgpData.Uuid) + + updatedBgpRP = bgpRPCreate.RoutingProtocolBgpData + if _, err = waitUntilRoutingProtocolIsProvisioned(bgpRPCreate.RoutingProtocolBgpData.Uuid, connectionUUID, meta, ctx); err != nil { + return diag.Errorf("error waiting for Routing Protocol %s to be created: %s", bgpRPCreate.RoutingProtocolBgpData.Uuid, err) + } + } + } + } + + routingProtocols := RoutingProtocols{ + connectionUUID: connectionUUID, + directRoutingProtocol: updatedDirectRP, + bgpRoutingProtocol: updatedBgpRP, + } + + return setFabricRoutingProtocolsMap(d, routingProtocols) +} + +func resourceFabricRoutingProtocolsDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*Config).fabricClient + ctx = context.WithValue(ctx, v4.ContextAccessToken, meta.(*Config).FabricAuthToken) + connectionUUID := d.Get("connection_uuid").(string) + directRPUUID, bgpRPUUID := readUUIDsFromId(d.Id()) + + if bgpRPUUID != "" { + _, _, err := client.RoutingProtocolsApi.DeleteConnectionRoutingProtocolByUuid(ctx, bgpRPUUID, connectionUUID) + if err != nil { + errors, ok := err.(v4.GenericSwaggerError).Model().([]v4.ModelError) + if ok { + // EQ-3142509 = Connection already deleted + if hasModelErrorCode(errors, "EQ-3142509") { + return diag.Diagnostics{} + } + } + return diag.FromErr(fmt.Errorf("error deleting routing protocol (%s): %v", bgpRPUUID, err)) + } + + err = waitUntilRoutingProtocolsIsDeprovisioned(bgpRPUUID, connectionUUID, meta, ctx) + if err != nil { + return diag.FromErr(fmt.Errorf("error waiting for routing protocol (%s) deletion: %v", bgpRPUUID, err)) + } + } + _, _, err := client.RoutingProtocolsApi.DeleteConnectionRoutingProtocolByUuid(ctx, directRPUUID, connectionUUID) + if err != nil { + errors, ok := err.(v4.GenericSwaggerError).Model().([]v4.ModelError) + if ok { + // EQ-3142509 = Connection already deleted + if hasModelErrorCode(errors, "EQ-3142509") { + return diag.Diagnostics{} + } + } + return diag.FromErr(fmt.Errorf("error deleting routing protocol (%s): %v", directRPUUID, err)) + } + + err = waitUntilRoutingProtocolsIsDeprovisioned(directRPUUID, connectionUUID, meta, ctx) + if err != nil { + return diag.FromErr(fmt.Errorf("error waiting for routing protocol (%s) deletion: %v", directRPUUID, err)) + } + + return diag.Diagnostics{} +} + +func waitUntilRoutingProtocolsIsProvisioned(uuid string, connUuid string, meta interface{}, ctx context.Context) (v4.RoutingProtocolData, error) { + log.Printf("Waiting for Routing Protocol(s) %s to be provisioned", uuid) + stateConf := &resource.StateChangeConf{ + Pending: []string{ + string(v4.PROVISIONING_ConnectionState), + string(v4.REPROVISIONING_ConnectionState), + }, + Target: []string{ + string(v4.PROVISIONED_ConnectionState), + }, + Refresh: func() (interface{}, string, error) { + client := meta.(*Config).fabricClient + dbConn, _, err := client.RoutingProtocolsApi.GetConnectionRoutingProtocolByUuid(ctx, uuid, connUuid) + if err != nil { + return "", "", err + } + var state string + if dbConn.Type_ == "BGP" { + state = dbConn.RoutingProtocolBgpData.State + } else if dbConn.Type_ == "DIRECT" { + state = dbConn.RoutingProtocolDirectData.State + } + return dbConn, state, nil + }, + Timeout: 5 * time.Minute, + Delay: 30 * time.Second, + MinTimeout: 30 * time.Second, + } + + inter, err := stateConf.WaitForStateContext(ctx) + dbConn := v4.RoutingProtocolData{} + + if err == nil { + dbConn = inter.(v4.RoutingProtocolData) + } + + return dbConn, err +} + +func waitUntilRoutingProtocolsIsDeprovisioned(uuid string, connUuid string, meta interface{}, ctx context.Context) error { + log.Printf("Waiting for routing protocol to be deprovisioned, uuid %s", uuid) + + /* check if resource is not found */ + stateConf := &resource.StateChangeConf{ + Target: []string{ + strconv.Itoa(404), + }, + Refresh: func() (interface{}, string, error) { + client := meta.(*Config).fabricClient + dbConn, resp, _ := client.RoutingProtocolsApi.GetConnectionRoutingProtocolByUuid(ctx, uuid, connUuid) + // fixme: check for error code instead? + // ignore error for Target + return dbConn, strconv.Itoa(resp.StatusCode), nil + }, + Timeout: 5 * time.Minute, + Delay: 30 * time.Second, + MinTimeout: 30 * time.Second, + } + + _, err := stateConf.WaitForStateContext(ctx) + return err +} + +func waitForRoutingProtocolsUpdateCompletion(rpChangeUuid string, uuid string, connUuid string, meta interface{}, ctx context.Context) (v4.RoutingProtocolChangeData, error) { + log.Printf("Waiting for routing protocol update to complete, uuid %s", uuid) + stateConf := &resource.StateChangeConf{ + Target: []string{"COMPLETED"}, + Refresh: func() (interface{}, string, error) { + client := meta.(*Config).fabricClient + dbConn, _, err := client.RoutingProtocolsApi.GetConnectionRoutingProtocolsChangeByUuid(ctx, connUuid, uuid, rpChangeUuid) + if err != nil { + return "", "", err + } + updatableState := "" + if dbConn.Status == "COMPLETED" { + updatableState = dbConn.Status + } + return dbConn, updatableState, nil + }, + Timeout: 2 * time.Minute, + Delay: 30 * time.Second, + MinTimeout: 30 * time.Second, + } + + inter, err := stateConf.WaitForStateContext(ctx) + dbConn := v4.RoutingProtocolChangeData{} + + if err == nil { + dbConn = inter.(v4.RoutingProtocolChangeData) + } + return dbConn, err +} diff --git a/equinix/resource_fabric_routing_protocols_acc_test.go b/equinix/resource_fabric_routing_protocols_acc_test.go new file mode 100644 index 000000000..3671d0fe1 --- /dev/null +++ b/equinix/resource_fabric_routing_protocols_acc_test.go @@ -0,0 +1,105 @@ +package equinix + +import ( + "context" + "fmt" + "testing" + + v4 "github.com/equinix-labs/fabric-go/fabric/v4" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccFabricCreateRoutingProtocols(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: checkRoutingProtocolsDelete, + Steps: []resource.TestStep{ + { + Config: testAccFabricCreateRoutingProtocolsConfig(7, 3), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckTypeSetElemNestedAttrs("equinix_fabric_routing_protocols.test", "direct_routing_protocol.direct_ipv4.*", map[string]string{ + "equinix_iface_ip": fmt.Sprintf("190.1.1.1/29"), + }), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccFabricCreateRoutingProtocolsConfig(i, y int) string { + return fmt.Sprintf(`resource "equinix_fabric_routing_protocols" "test" { + connection_uuid = "b0eb3892-6404-442b-85c0-4f8fcf53d123" + + direct_routing_protocol { + name = "Direct-%d" + direct_ipv4 { + equinix_iface_ip = "190.1.1.1/29" + } + direct_ipv6 { + equinix_iface_ip = "190::1:1/126" + } + } + + bgp_routing_protocol { + name = "FCR-Con-BGP-RP" + bgp_ipv4 { + customer_peer_ip = "190.1.1.%d" + enabled = true + } + bgp_ipv6 { + customer_peer_ip = "190::1:2" + enabled = true + } + customer_asn = "100" + equinix_asn = "1345" + } + }`, i, y) +} + +func checkRoutingProtocolsDelete(s *terraform.State) error { + ctx := context.Background() + ctx = context.WithValue(ctx, v4.ContextAccessToken, testAccProvider.Meta().(*Config).FabricAuthToken) + for _, rs := range s.RootModule().Resources { + if rs.Type != "equinix_fabric_routing_protocols" { + continue + } + err := waitUntilRoutingProtocolIsDeprovisioned(rs.Primary.ID, rs.Primary.Attributes["connection_uuid"], testAccProvider.Meta(), ctx) + if err != nil { + return fmt.Errorf("API call failed while waiting for resource deletion") + } + } + return nil +} + +func TestAccFabricReadRoutingProtocolsByUuid(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccFabricReadRoutingProtocolsConfig("d7ab9902-4d89-4fc0-9a5f-9faea39d82a3", "6534071e-6216-4943-b8c4-1d2f2f0cbb07", "c1c052d0-8da9-4918-94fc-3e507f3ebf6a"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "equinix_fabric_routing_protocols.test", "direct_routing_protocol.type", fmt.Sprint("DIRECT")), + resource.TestCheckResourceAttr( + "equinix_fabric_routing_protocols.test", "direct_routing_protocol.state", fmt.Sprint("PROVISIONED")), + resource.TestCheckResourceAttr( + "equinix_fabric_routing_protocols.test", "bgp_routing_protocol.type", fmt.Sprint("BGP")), + resource.TestCheckResourceAttr( + "equinix_fabric_routing_protocols.test", "bgp_routing_protocol.state", fmt.Sprint("PROVISIONED")), + ), + }, + }, + }) +} + +func testAccFabricReadRoutingProtocolsConfig(connectionUuid, directRoutingProtocolUUID, bgpRoutingProtocolUUID string) string { + return fmt.Sprintf(`data "equinix_fabric_routing_protocols" "test" { + connection_uuid = "%s" + direct_routing_protocol_uuid = "%s" + bgp_routing_protocol_uuid = "%s" + }`, connectionUuid, directRoutingProtocolUUID, bgpRoutingProtocolUUID) +} diff --git a/examples/fabric/v4/cloudRouterConnectivity/routing-protocols/README.md b/examples/fabric/v4/cloudRouterConnectivity/routing-protocols/README.md new file mode 100644 index 000000000..95e21aabb --- /dev/null +++ b/examples/fabric/v4/cloudRouterConnectivity/routing-protocols/README.md @@ -0,0 +1,37 @@ +# ECX Fabric Cloud Router Connection Routing Protocols CRUD operations +This example shows how to create Routing Protocols on FCR connection . + +## Define values for the Fabric Cloud Router create +At minimum, you must set below variables in `terraform.tfvars` file: +- `equinix_client_id` - Equinix client ID (consumer key), obtained after + registering app in the developer platform +- `equinix_client_secret` - Equinix client secret ID (consumer secret), + obtained same way as above +- `connection_uuid`- Connection uuid to apply the routing details to +- `direct_rp_name`- Name of Direct Routing Protocol instance +- `equinix_ipv4_ip` - IPv4 for Direct Routing Protocol +- `equinix_ipv6_ip` - IPv6 for Direct Routing Protocol +- `bgp_rp_name` - Name of BGP Routing Protocol instance +- `customer_peer_ipv4` - Customer Peering IPv4 for BGP Routing Protocol +- `customer_peer_ipv6` - Customer Peering IPv6 for BGP Routing Protocol +- `bgp_enabled_ipv4` - Enable flag for IPv4 on BGP Routing Protocol instance +- `bgp_enabled_ipv6` - Enable flag for IPv6 on BGP Routing Protocol instance +- `customer_asn` - Customer BGP ASN Number +- `equinix_asn` - Equinix BGP ASN Number (Will default if not supplied) + +## Initialize +- First step is to initialize the terraform directory/resource we are going to work on. + In the given example, the folder to perform CRUD operations on an RP resource can be found at examples/routing-protocol-bgp/. + +- Change directory into - `CD examples/fabric/v4/cloudRouterConnectivity/routing-protocols/` +- Initialize Terraform plugins - `terraform init` + +## Routing-protocol BGP : Create, Read, Update and Delete(CRUD) operations +Note: `–auto-approve` command does not prompt the user for validating the applying config. Remove it to get a prompt to confirm the operation. + +| Operation | Command | Description | +|:----------|:---------------------------------:|-----------------------------------------------------------------------------------------:| +| CREATE | `terraform apply –auto-approve` | Creates a routing_protocols resource | +| READ | `terraform show` | Reads/Shows the current state of the routing_protocols resource | +| UPDATE | `terraform apply -refresh` | Updates the routing_protocols resource with values provided in the terraform.tfvars file | +| DELETE | `terraform destroy –auto-approve` | Deletes the created routing_protocols resource | \ No newline at end of file diff --git a/examples/fabric/v4/cloudRouterConnectivity/routing-protocols/main.tf b/examples/fabric/v4/cloudRouterConnectivity/routing-protocols/main.tf new file mode 100644 index 000000000..1cce2c7f4 --- /dev/null +++ b/examples/fabric/v4/cloudRouterConnectivity/routing-protocols/main.tf @@ -0,0 +1,39 @@ +provider "equinix" { + client_id = var.equinix_client_id + client_secret = var.equinix_client_secret +} + +resource "equinix_fabric_routing_protocols" "routing_protocols" { + connection_uuid = var.connection_uuid + + direct_routing_protocol { + name = var.direct_rp_name + direct_ipv4 { + equinix_iface_ip = var.equinix_ipv4_ip + } + direct_ipv6 { + equinix_iface_ip = var.equinix_ipv6_ip + } + } + + bgp_routing_protocol { + name = var.bgp_rp_name + bgp_ipv4 { + customer_peer_ip = var.customer_peer_ipv4 + enabled = var.bgp_enabled_ipv4 + } + bgp_ipv6 { + customer_peer_ip = var.customer_peer_ipv6 + enabled = var.bgp_enabled_ipv6 + } + customer_asn = var.customer_asn + equinix_asn = var.equinix_asn + } +} + + + +output "rp_result" { + value = equinix_fabric_routing_protocols.routing_protocols.id +} + diff --git a/examples/fabric/v4/cloudRouterConnectivity/routing-protocols/terraform.tf b/examples/fabric/v4/cloudRouterConnectivity/routing-protocols/terraform.tf new file mode 100644 index 000000000..852cb3642 --- /dev/null +++ b/examples/fabric/v4/cloudRouterConnectivity/routing-protocols/terraform.tf @@ -0,0 +1,8 @@ +terraform { + required_providers { + equinix = { + source = "equinix/equinix" + } + } +} + diff --git a/examples/fabric/v4/cloudRouterConnectivity/routing-protocols/terraform.tfvars.example b/examples/fabric/v4/cloudRouterConnectivity/routing-protocols/terraform.tfvars.example new file mode 100644 index 000000000..d3a75cb8b --- /dev/null +++ b/examples/fabric/v4/cloudRouterConnectivity/routing-protocols/terraform.tfvars.example @@ -0,0 +1,17 @@ +equinix_client_id = "MyEquinixClientId" +equinix_client_secret = "MyEquinixSecret" + +connection_uuid = "b0eb3892-6404-442b-85c0-4f8fcf53d55b" + +direct_rp_name = "FCR-Con-Direct-RP" +equinix_ipv4_ip = "190.1.1.1/30" +equinix_ipv6_ip = "190::1:1/126" + + +bgp_rp_name = "FCR-Con-BGP-RP" +customer_peer_ipv4 = "190.1.1.2" +bgp_ipv4_enabled = true +customer_peer_ipv6 = "190::1:2" +bgp_ipv6_enabled = true +equinix_asn = "1345" +customer_asn = "100" \ No newline at end of file diff --git a/examples/fabric/v4/cloudRouterConnectivity/routing-protocols/variables.tf b/examples/fabric/v4/cloudRouterConnectivity/routing-protocols/variables.tf new file mode 100644 index 000000000..a83986360 --- /dev/null +++ b/examples/fabric/v4/cloudRouterConnectivity/routing-protocols/variables.tf @@ -0,0 +1,16 @@ +variable "equinix_client_id" {} +variable "equinix_client_secret" {} + +variable "connection_uuid" {} + +variable "direct_rp_name" {} +variable "equinix_ipv4_ip" {} +variable "equinix_ipv6_ip" {} + +variable "bgp_rp_name" {} +variable "customer_peer_ipv4" {} +variable "customer_peer_ipv6" {} +variable "bgp_enabled_ipv4" {} +variable "bgp_enabled_ipv6" {} +variable "customer_asn" {} +variable "equinix_asn" {} From 5a4feaafb642e67dfa7c38557b1c1f8bb9b30148 Mon Sep 17 00:00:00 2001 From: Tim Hogarty Date: Thu, 2 Nov 2023 00:10:42 -0700 Subject: [PATCH 2/4] Update id when creation fails --- equinix/resource_fabric_routing_protocols.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/equinix/resource_fabric_routing_protocols.go b/equinix/resource_fabric_routing_protocols.go index 3779f5199..ce4047b47 100644 --- a/equinix/resource_fabric_routing_protocols.go +++ b/equinix/resource_fabric_routing_protocols.go @@ -197,9 +197,11 @@ func resourceFabricRoutingProtocolsCreate(ctx context.Context, d *schema.Resourc d.SetId(id) directRPUUID, bgpRPUUID := readUUIDsFromId(id) if _, err = waitUntilRoutingProtocolIsProvisioned(directRPUUID, connectionUUID, meta, ctx); err != nil { + d.SetId("") return diag.Errorf("error waiting for Routing Protocol %s to be created: %s", d.Id(), err) } if _, err = waitUntilRoutingProtocolIsProvisioned(bgpRPUUID, connectionUUID, meta, ctx); err != nil { + d.SetId(directRPUUID) return diag.Errorf("error waiting for Routing Protocol %s to be created: %s", d.Id(), err) } } else { @@ -210,6 +212,7 @@ func resourceFabricRoutingProtocolsCreate(ctx context.Context, d *schema.Resourc d.SetId(fabricRoutingProtocol.RoutingProtocolDirectData.Uuid) if _, err = waitUntilRoutingProtocolIsProvisioned(fabricRoutingProtocol.RoutingProtocolDirectData.Uuid, connectionUUID, meta, ctx); err != nil { + d.SetId("") return diag.Errorf("error waiting for Routing Protocol %s to be created: %s", d.Id(), err) } } From b8d1189cf371044e3dc224d6de6b626ca5fff7cc Mon Sep 17 00:00:00 2001 From: Tim Hogarty Date: Fri, 10 Nov 2023 23:56:35 -0800 Subject: [PATCH 3/4] Update gateway reference, acceptance test, and docs for routing_protocols resource --- docs/resources/equinix_fabric_routing_protocols.md | 2 +- equinix/errors.go | 3 --- equinix/fabric_mapping_helper.go | 1 + equinix/resource_fabric_routing_protocols_acc_test.go | 3 +++ 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/resources/equinix_fabric_routing_protocols.md b/docs/resources/equinix_fabric_routing_protocols.md index dc7955299..aa73db2fb 100644 --- a/docs/resources/equinix_fabric_routing_protocols.md +++ b/docs/resources/equinix_fabric_routing_protocols.md @@ -4,7 +4,7 @@ page_title: "equinix_fabric_routing_protocols Resource - terraform-provider-equi subcategory: "Fabric" description: |- Fabric V4 API compatible resource allows creation and management of Equinix Fabric routing protocols - ~> Note Equinix Fabric v4 resources and datasources are currently in Beta. The interfaces related to equinix_fabric_ resources and datasources may change ahead of general availability. Please, do not hesitate to report any problems that you experience by opening a new issue https://github.com/equinix/terraform-provider-equinix/issues/new?template=bug.md + ~> Note Equinix Fabric v4 resources and datasources are currently in Beta. The interfaces related to equinix_fabric_ resources and datasources may change ahead of general availability. Please, do not hesitate to report any problems that you experience by opening a new issue https://github.com/equinix/terraform-provider-equinix/issues/new?assignees=&labels=bug&projects=&template=bug.yml&title=%5BBug%5D%3A+equinix_fabric_routing_protocols --- # equinix_fabric_routing_protocols (Resource) diff --git a/equinix/errors.go b/equinix/errors.go index b20ba5517..c6292c327 100644 --- a/equinix/errors.go +++ b/equinix/errors.go @@ -97,9 +97,6 @@ func setMap(d *schema.ResourceData, m map[string]interface{}) error { if f, ok := v.(setFn); ok { err = f(d, key) } else { - if key == "router" { - err = d.Set("gateway", v) - } err = d.Set(key, v) } diff --git a/equinix/fabric_mapping_helper.go b/equinix/fabric_mapping_helper.go index a27f70fd5..50424c8d7 100644 --- a/equinix/fabric_mapping_helper.go +++ b/equinix/fabric_mapping_helper.go @@ -693,6 +693,7 @@ func accessPointToTerra(accessPoint *v4.AccessPoint) *schema.Set { } if accessPoint.Router != nil { mappedAccessPoint["router"] = CloudRouterToTerra(accessPoint.Router) + mappedAccessPoint["gateway"] = CloudRouterToTerra(accessPoint.Router) } if accessPoint.LinkProtocol != nil { mappedAccessPoint["link_protocol"] = linkedProtocolToTerra(*accessPoint.LinkProtocol) diff --git a/equinix/resource_fabric_routing_protocols_acc_test.go b/equinix/resource_fabric_routing_protocols_acc_test.go index 3671d0fe1..6dad34f42 100644 --- a/equinix/resource_fabric_routing_protocols_acc_test.go +++ b/equinix/resource_fabric_routing_protocols_acc_test.go @@ -24,6 +24,9 @@ func TestAccFabricCreateRoutingProtocols(t *testing.T) { }), ), ExpectNonEmptyPlan: true, + ImportStateId: "//", + ImportState: true, + ImportStateVerify: true, }, }, }) From e2d7c1fb3ac0da71dedf0f1e4b5e898a7cf2a5fd Mon Sep 17 00:00:00 2001 From: Tim Hogarty Date: Sat, 11 Nov 2023 00:15:31 -0800 Subject: [PATCH 4/4] Update docs to create correctly named github issue --- docs/data-sources/equinix_fabric_routing_protocols.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data-sources/equinix_fabric_routing_protocols.md b/docs/data-sources/equinix_fabric_routing_protocols.md index 9c8325645..c72703760 100644 --- a/docs/data-sources/equinix_fabric_routing_protocols.md +++ b/docs/data-sources/equinix_fabric_routing_protocols.md @@ -4,7 +4,7 @@ page_title: "equinix_fabric_routing_protocols Resource - terraform-provider-equi subcategory: "Fabric" description: |- Fabric V4 API compatible data resource that allows user to fetch routing protocols for a given routing protocol ids -~> Note Equinix Fabric v4 resources and datasources are currently in Beta. The interfaces related to equinix_fabric_ resources and datasources may change ahead of general availability. Please, do not hesitate to report any problems that you experience by opening a new issue https://github.com/equinix/terraform-provider-equinix/issues/new?template=bug.md +~> Note Equinix Fabric v4 resources and datasources are currently in Beta. The interfaces related to equinix_fabric_ resources and datasources may change ahead of general availability. Please, do not hesitate to report any problems that you experience by opening a new issue https://github.com/equinix/terraform-provider-equinix/issues/new?assignees=&labels=bug&projects=&template=bug.yml&title=%5BBug%5D%3A+equinix_fabric_routing_protocols --- # equinix_fabric_routing_protocols (Data Source)