From f2f699488108369775fa901cdb0b0397128d45a2 Mon Sep 17 00:00:00 2001
From: thogarty <139183873+thogarty@users.noreply.github.com>
Date: Mon, 14 Oct 2024 12:27:50 -0700
Subject: [PATCH] feat: fabric resource connection_route_filter (#795)
* Add equinix_fabric_connection_route_filter resource to attach route
filter policies to fabric cloud router connections
* Add data source for retrieving connection_route_filters by connection
and route filter uuids
* Add data source to get all route filters for a given connection uuid
* Add docs with make docs
* Add acceptance tests for resource and data sources
Local tests passing:
---
.../fabric_connection_route_filter.md | 57 +++++
.../fabric_connection_route_filters.md | 75 +++++++
.../fabric_connection_route_filter.md | 72 +++++++
equinix/provider.go | 56 ++---
.../data-source.tf | 24 +++
.../data-source.tf | 23 ++
.../resource.tf | 25 +++
.../connection_route_filter/datasources.go | 56 +++++
.../datasources_schema.go | 110 ++++++++++
.../datasources_test.go | 3 +
.../fabric/connection_route_filter/models.go | 67 ++++++
.../connection_route_filter/resource.go | 203 ++++++++++++++++++
.../resource_schema.go | 49 +++++
.../connection_route_filter/resource_test.go | 196 +++++++++++++++++
14 files changed, 990 insertions(+), 26 deletions(-)
create mode 100644 docs/data-sources/fabric_connection_route_filter.md
create mode 100644 docs/data-sources/fabric_connection_route_filters.md
create mode 100644 docs/resources/fabric_connection_route_filter.md
create mode 100644 examples/data-sources/equinix_fabric_connection_route_filter/data-source.tf
create mode 100644 examples/data-sources/equinix_fabric_connection_route_filters/data-source.tf
create mode 100644 examples/resources/equinix_fabric_connection_route_filter/resource.tf
create mode 100644 internal/resources/fabric/connection_route_filter/datasources.go
create mode 100644 internal/resources/fabric/connection_route_filter/datasources_schema.go
create mode 100644 internal/resources/fabric/connection_route_filter/datasources_test.go
create mode 100644 internal/resources/fabric/connection_route_filter/models.go
create mode 100644 internal/resources/fabric/connection_route_filter/resource.go
create mode 100644 internal/resources/fabric/connection_route_filter/resource_schema.go
create mode 100644 internal/resources/fabric/connection_route_filter/resource_test.go
diff --git a/docs/data-sources/fabric_connection_route_filter.md b/docs/data-sources/fabric_connection_route_filter.md
new file mode 100644
index 000000000..2511ed554
--- /dev/null
+++ b/docs/data-sources/fabric_connection_route_filter.md
@@ -0,0 +1,57 @@
+---
+subcategory: "Fabric"
+---
+
+# equinix_fabric_connection_route_filter (Data Source)
+
+Fabric V4 API compatible data resource that allow user to fetch route filter policy attachment to a fabric connection
+
+Additional Documentation:
+* Getting Started: https://docs.equinix.com/en-us/Content/Interconnection/FCR/FCR-route-filters.htm
+* API: https://developer.equinix.com/dev-docs/fabric/api-reference/fabric-v4-apis#route-filter-rules
+
+## Example Usage
+
+```terraform
+data "equinix_fabric_connection_route_filter" "attached_policy" {
+ connection_id = ""
+ route_filter_id = ""
+}
+
+output "connection_route_filter_id" {
+ value = data.equinix_fabric_connection_route_filter.attached_policy.id
+}
+
+output "connection_route_filter_connection_id" {
+ value = data.equinix_fabric_connection_route_filter.attached_policy.connection_id
+}
+
+output "connection_route_filter_direction" {
+ value = data.equinix_fabric_connection_route_filter.attached_policy.direction
+}
+
+output "connection_route_filter_type" {
+ value = data.equinix_fabric_connection_route_filter.attached_policy.type
+}
+
+output "connection_route_filter_attachment_status" {
+ value = data.equinix_fabric_connection_route_filter.attached_policy.attachment_status
+}
+```
+
+
+## Schema
+
+### Required
+
+- `connection_id` (String) Equinix Assigned UUID of the Equinix Connection to attach the Route Filter Policy to
+- `route_filter_id` (String) Equinix Assigned UUID of the Route Filter Policy to attach to the Equinix Connection
+
+### Read-Only
+
+- `attachment_status` (String) Status of the Route Filter Policy attachment lifecycle
+- `direction` (String) Direction of the filtering of the attached Route Filter Policy
+- `href` (String) URI to the attached Route Filter Policy on the Connection
+- `id` (String) The ID of this resource.
+- `type` (String) Route Filter Type. One of [ "BGP_IPv4_PREFIX_FILTER", "BGP_IPv6_PREFIX_FILTER" ]
+- `uuid` (String) Equinix Assigned ID for Route Filter Policy
diff --git a/docs/data-sources/fabric_connection_route_filters.md b/docs/data-sources/fabric_connection_route_filters.md
new file mode 100644
index 000000000..e6da98fce
--- /dev/null
+++ b/docs/data-sources/fabric_connection_route_filters.md
@@ -0,0 +1,75 @@
+---
+subcategory: "Fabric"
+---
+
+# equinix_fabric_connection_route_filters (Data Source)
+
+Fabric V4 API compatible data resource that allow user to fetch all route filter policies attached to a fabric connection
+
+Additional Documentation:
+* Getting Started: https://docs.equinix.com/en-us/Content/Interconnection/FCR/FCR-route-filters.htm
+* API: https://developer.equinix.com/dev-docs/fabric/api-reference/fabric-v4-apis#route-filter-rules
+
+## Example Usage
+
+```terraform
+data "equinix_connection_route_filters" "attached_policies" {
+ connection_id = ""
+}
+
+output "connection_first_route_filter_uuid" {
+ value = data.equinix_fabric_connection_route_filter.attached_policies.0.uuid
+}
+
+output "connection_first_route_filter_connection_id" {
+ value = data.equinix_fabric_connection_route_filter.attached_policies.0.connection_id
+}
+
+output "connection_first_route_filter_direction" {
+ value = data.equinix_fabric_connection_route_filter.attached_policies.0.direction
+}
+
+output "connection_first_route_filter_type" {
+ value = data.equinix_fabric_connection_route_filter.attached_policies.0.type
+}
+
+output "connection_first_route_filter_attachment_status" {
+ value = data.equinix_fabric_connection_route_filter.attached_policies.0.attachment_status
+}
+```
+
+
+## Schema
+
+### Required
+
+- `connection_id` (String) Equinix Assigned UUID of the Equinix Connection to attach the Route Filter Policy to
+
+### Read-Only
+
+- `data` (List of Object) The list of Rules attached to the given Route Filter Policy UUID (see [below for nested schema](#nestedatt--data))
+- `id` (String) The ID of this resource.
+- `pagination` (Set of Object) Pagination details for the Data Source Search Request (see [below for nested schema](#nestedatt--pagination))
+
+
+### Nested Schema for `data`
+
+Read-Only:
+
+- `attachment_status` (String)
+- `direction` (String)
+- `href` (String)
+- `type` (String)
+- `uuid` (String)
+
+
+
+### Nested Schema for `pagination`
+
+Read-Only:
+
+- `limit` (Number)
+- `next` (String)
+- `offset` (Number)
+- `previous` (String)
+- `total` (Number)
diff --git a/docs/resources/fabric_connection_route_filter.md b/docs/resources/fabric_connection_route_filter.md
new file mode 100644
index 000000000..302735413
--- /dev/null
+++ b/docs/resources/fabric_connection_route_filter.md
@@ -0,0 +1,72 @@
+---
+subcategory: "Fabric"
+---
+
+# equinix_fabric_connection_route_filter (Resource)
+
+Fabric V4 API compatible resource allows attachment of Route Filter Polices to Fabric Connections
+
+Additional Documentation:
+* Getting Started: https://docs.equinix.com/en-us/Content/Interconnection/FCR/FCR-route-filters.htm
+* API: https://developer.equinix.com/dev-docs/fabric/api-reference/fabric-v4-apis#route-filters
+
+## Example Usage
+
+```terraform
+resource "equinix_fabric_connection_route_filter" "policy_attachment" {
+ connection_id = ""
+ route_filter_id = ""
+ direction = "INBOUND"
+}
+
+output "connection_route_filter_id" {
+ value = equinix_fabric_connection_route_filter.policy_attachment.id
+}
+
+output "connection_route_filter_connection_id" {
+ value = equinix_fabric_connection_route_filter.policy_attachment.connection_id
+}
+
+output "connection_route_filter_direction" {
+ value = equinix_fabric_connection_route_filter.policy_attachment.direction
+}
+
+output "connection_route_filter_type" {
+ value = equinix_fabric_connection_route_filter.policy_attachment.type
+}
+
+output "connection_route_filter_attachment_status" {
+ value = equinix_fabric_connection_route_filter.policy_attachment.attachment_status
+}
+```
+
+
+## Schema
+
+### Required
+
+- `connection_id` (String) Equinix Assigned UUID of the Equinix Connection to attach the Route Filter Policy to
+- `direction` (String) Direction of the filtering of the attached Route Filter Policy
+- `route_filter_id` (String) Equinix Assigned UUID of the Route Filter Policy to attach to the Equinix Connection
+
+### Optional
+
+- `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts))
+
+### Read-Only
+
+- `attachment_status` (String) Status of the Route Filter Policy attachment lifecycle
+- `href` (String) URI to the attached Route Filter Policy on the Connection
+- `id` (String) The ID of this resource.
+- `type` (String) Route Filter Type. One of [ "BGP_IPv4_PREFIX_FILTER", "BGP_IPv6_PREFIX_FILTER" ]
+- `uuid` (String) Equinix Assigned ID for Route Filter Policy
+
+
+### Nested Schema for `timeouts`
+
+Optional:
+
+- `create` (String)
+- `delete` (String)
+- `read` (String)
+- `update` (String)
diff --git a/equinix/provider.go b/equinix/provider.go
index 2ff55f43d..bf38b2592 100644
--- a/equinix/provider.go
+++ b/equinix/provider.go
@@ -8,6 +8,7 @@ import (
"github.com/equinix/terraform-provider-equinix/internal/config"
fabric_connection "github.com/equinix/terraform-provider-equinix/internal/resources/fabric/connection"
+ fabric_connection_route_filter "github.com/equinix/terraform-provider-equinix/internal/resources/fabric/connection_route_filter"
fabric_market_place_subscription "github.com/equinix/terraform-provider-equinix/internal/resources/fabric/marketplace"
fabric_network "github.com/equinix/terraform-provider-equinix/internal/resources/fabric/network"
fabric_route_filter "github.com/equinix/terraform-provider-equinix/internal/resources/fabric/route_filter"
@@ -87,6 +88,8 @@ func Provider() *schema.Provider {
"equinix_fabric_routing_protocol": dataSourceRoutingProtocol(),
"equinix_fabric_connection": fabric_connection.DataSource(),
"equinix_fabric_connections": fabric_connection.DataSourceSearch(),
+ "equinix_fabric_connection_route_filter": fabric_connection_route_filter.DataSource(),
+ "equinix_fabric_connection_route_filters": fabric_connection_route_filter.DataSourceGetAllRules(),
"equinix_fabric_cloud_router": dataSourceFabricCloudRouter(),
"equinix_fabric_cloud_routers": dataSourceFabricGetCloudRouters(),
"equinix_fabric_market_place_subscription": fabric_market_place_subscription.DataSourceFabricMarketplaceSubscription(),
@@ -125,33 +128,34 @@ func Provider() *schema.Provider {
"equinix_metal_vrf": vrf.DataSource(),
},
ResourcesMap: map[string]*schema.Resource{
- "equinix_fabric_network": fabric_network.Resource(),
- "equinix_fabric_cloud_router": resourceFabricCloudRouter(),
- "equinix_fabric_connection": fabric_connection.Resource(),
- "equinix_fabric_route_filter": fabric_route_filter.Resource(),
- "equinix_fabric_route_filter_rule": fabric_route_filter_rule.Resource(),
- "equinix_fabric_routing_protocol": resourceFabricRoutingProtocol(),
- "equinix_fabric_service_profile": resourceFabricServiceProfile(),
+ "equinix_fabric_network": fabric_network.Resource(),
+ "equinix_fabric_cloud_router": resourceFabricCloudRouter(),
+ "equinix_fabric_connection": fabric_connection.Resource(),
+ "equinix_fabric_connection_route_filter": fabric_connection_route_filter.Resource(),
+ "equinix_fabric_route_filter": fabric_route_filter.Resource(),
+ "equinix_fabric_route_filter_rule": fabric_route_filter_rule.Resource(),
+ "equinix_fabric_routing_protocol": resourceFabricRoutingProtocol(),
+ "equinix_fabric_service_profile": resourceFabricServiceProfile(),
"equinix_fabric_service_token": fabric_service_token.Resource(),
- "equinix_network_device": resourceNetworkDevice(),
- "equinix_network_ssh_user": resourceNetworkSSHUser(),
- "equinix_network_bgp": resourceNetworkBGP(),
- "equinix_network_ssh_key": resourceNetworkSSHKey(),
- "equinix_network_acl_template": resourceNetworkACLTemplate(),
- "equinix_network_device_link": resourceNetworkDeviceLink(),
- "equinix_network_file": resourceNetworkFile(),
- "equinix_metal_user_api_key": resourceMetalUserAPIKey(),
- "equinix_metal_project_api_key": resourceMetalProjectAPIKey(),
- "equinix_metal_device": metal_device.Resource(),
- "equinix_metal_device_network_type": resourceMetalDeviceNetworkType(),
- "equinix_metal_port": metal_port.Resource(),
- "equinix_metal_reserved_ip_block": resourceMetalReservedIPBlock(),
- "equinix_metal_ip_attachment": resourceMetalIPAttachment(),
- "equinix_metal_spot_market_request": resourceMetalSpotMarketRequest(),
- "equinix_metal_virtual_circuit": virtual_circuit.Resource(),
- "equinix_metal_vrf": vrf.Resource(),
- "equinix_metal_bgp_session": resourceMetalBGPSession(),
- "equinix_metal_port_vlan_attachment": resourceMetalPortVlanAttachment(),
+ "equinix_network_device": resourceNetworkDevice(),
+ "equinix_network_ssh_user": resourceNetworkSSHUser(),
+ "equinix_network_bgp": resourceNetworkBGP(),
+ "equinix_network_ssh_key": resourceNetworkSSHKey(),
+ "equinix_network_acl_template": resourceNetworkACLTemplate(),
+ "equinix_network_device_link": resourceNetworkDeviceLink(),
+ "equinix_network_file": resourceNetworkFile(),
+ "equinix_metal_user_api_key": resourceMetalUserAPIKey(),
+ "equinix_metal_project_api_key": resourceMetalProjectAPIKey(),
+ "equinix_metal_device": metal_device.Resource(),
+ "equinix_metal_device_network_type": resourceMetalDeviceNetworkType(),
+ "equinix_metal_port": metal_port.Resource(),
+ "equinix_metal_reserved_ip_block": resourceMetalReservedIPBlock(),
+ "equinix_metal_ip_attachment": resourceMetalIPAttachment(),
+ "equinix_metal_spot_market_request": resourceMetalSpotMarketRequest(),
+ "equinix_metal_virtual_circuit": virtual_circuit.Resource(),
+ "equinix_metal_vrf": vrf.Resource(),
+ "equinix_metal_bgp_session": resourceMetalBGPSession(),
+ "equinix_metal_port_vlan_attachment": resourceMetalPortVlanAttachment(),
},
ProviderMetaSchema: map[string]*schema.Schema{
"module_name": {
diff --git a/examples/data-sources/equinix_fabric_connection_route_filter/data-source.tf b/examples/data-sources/equinix_fabric_connection_route_filter/data-source.tf
new file mode 100644
index 000000000..728d0d1e7
--- /dev/null
+++ b/examples/data-sources/equinix_fabric_connection_route_filter/data-source.tf
@@ -0,0 +1,24 @@
+data "equinix_fabric_connection_route_filter" "attached_policy" {
+ connection_id = ""
+ route_filter_id = ""
+}
+
+output "connection_route_filter_id" {
+ value = data.equinix_fabric_connection_route_filter.attached_policy.id
+}
+
+output "connection_route_filter_connection_id" {
+ value = data.equinix_fabric_connection_route_filter.attached_policy.connection_id
+}
+
+output "connection_route_filter_direction" {
+ value = data.equinix_fabric_connection_route_filter.attached_policy.direction
+}
+
+output "connection_route_filter_type" {
+ value = data.equinix_fabric_connection_route_filter.attached_policy.type
+}
+
+output "connection_route_filter_attachment_status" {
+ value = data.equinix_fabric_connection_route_filter.attached_policy.attachment_status
+}
diff --git a/examples/data-sources/equinix_fabric_connection_route_filters/data-source.tf b/examples/data-sources/equinix_fabric_connection_route_filters/data-source.tf
new file mode 100644
index 000000000..c2e22a893
--- /dev/null
+++ b/examples/data-sources/equinix_fabric_connection_route_filters/data-source.tf
@@ -0,0 +1,23 @@
+data "equinix_connection_route_filters" "attached_policies" {
+ connection_id = ""
+}
+
+output "connection_first_route_filter_uuid" {
+ value = data.equinix_fabric_connection_route_filter.attached_policies.0.uuid
+}
+
+output "connection_first_route_filter_connection_id" {
+ value = data.equinix_fabric_connection_route_filter.attached_policies.0.connection_id
+}
+
+output "connection_first_route_filter_direction" {
+ value = data.equinix_fabric_connection_route_filter.attached_policies.0.direction
+}
+
+output "connection_first_route_filter_type" {
+ value = data.equinix_fabric_connection_route_filter.attached_policies.0.type
+}
+
+output "connection_first_route_filter_attachment_status" {
+ value = data.equinix_fabric_connection_route_filter.attached_policies.0.attachment_status
+}
diff --git a/examples/resources/equinix_fabric_connection_route_filter/resource.tf b/examples/resources/equinix_fabric_connection_route_filter/resource.tf
new file mode 100644
index 000000000..6ea69b7d8
--- /dev/null
+++ b/examples/resources/equinix_fabric_connection_route_filter/resource.tf
@@ -0,0 +1,25 @@
+resource "equinix_fabric_connection_route_filter" "policy_attachment" {
+ connection_id = ""
+ route_filter_id = ""
+ direction = "INBOUND"
+}
+
+output "connection_route_filter_id" {
+ value = equinix_fabric_connection_route_filter.policy_attachment.id
+}
+
+output "connection_route_filter_connection_id" {
+ value = equinix_fabric_connection_route_filter.policy_attachment.connection_id
+}
+
+output "connection_route_filter_direction" {
+ value = equinix_fabric_connection_route_filter.policy_attachment.direction
+}
+
+output "connection_route_filter_type" {
+ value = equinix_fabric_connection_route_filter.policy_attachment.type
+}
+
+output "connection_route_filter_attachment_status" {
+ value = equinix_fabric_connection_route_filter.policy_attachment.attachment_status
+}
diff --git a/internal/resources/fabric/connection_route_filter/datasources.go b/internal/resources/fabric/connection_route_filter/datasources.go
new file mode 100644
index 000000000..e8eaeb309
--- /dev/null
+++ b/internal/resources/fabric/connection_route_filter/datasources.go
@@ -0,0 +1,56 @@
+package connection_route_filter
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/equinix/terraform-provider-equinix/internal/config"
+ equinix_errors "github.com/equinix/terraform-provider-equinix/internal/errors"
+
+ "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+)
+
+func DataSource() *schema.Resource {
+ return &schema.Resource{
+ ReadContext: dataSourceRead,
+ Schema: dataSourceByUUIDSchema(),
+ Description: `Fabric V4 API compatible data resource that allow user to fetch route filter policy attachment to a fabric connection
+
+Additional Documentation:
+* Getting Started: https://docs.equinix.com/en-us/Content/Interconnection/FCR/FCR-route-filters.htm
+* API: https://developer.equinix.com/dev-docs/fabric/api-reference/fabric-v4-apis#route-filter-rules`,
+ }
+}
+
+func dataSourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+ uuid := d.Get("route_filter_id").(string)
+ d.SetId(uuid)
+ return resourceRead(ctx, d, meta)
+}
+
+func DataSourceGetAllRules() *schema.Resource {
+ return &schema.Resource{
+ ReadContext: dataSourceGetAllFilters,
+ Schema: dataSourceAllFiltersSchema(),
+ Description: `Fabric V4 API compatible data resource that allow user to fetch all route filter policies attached to a fabric connection
+
+Additional Documentation:
+* Getting Started: https://docs.equinix.com/en-us/Content/Interconnection/FCR/FCR-route-filters.htm
+* API: https://developer.equinix.com/dev-docs/fabric/api-reference/fabric-v4-apis#route-filter-rules`,
+ }
+}
+
+func dataSourceGetAllFilters(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+ client := meta.(*config.Config).NewFabricClientForSDK(d)
+ connectionId := d.Get("connection_id").(string)
+ connectionRouteFilters, _, err := client.RouteFiltersApi.GetConnectionRouteFilters(ctx, connectionId).Execute()
+ if err != nil {
+ return diag.FromErr(equinix_errors.FormatFabricError(err))
+ }
+ if len(connectionRouteFilters.Data) < 1 {
+ return diag.FromErr(fmt.Errorf("no records are found for the connection (%s) - %d , please change the search criteria", connectionId, len(connectionRouteFilters.Data)))
+ }
+ d.SetId(connectionId)
+ return setConnectionRouteFilterData(d, connectionRouteFilters)
+}
diff --git a/internal/resources/fabric/connection_route_filter/datasources_schema.go b/internal/resources/fabric/connection_route_filter/datasources_schema.go
new file mode 100644
index 000000000..42da7fc92
--- /dev/null
+++ b/internal/resources/fabric/connection_route_filter/datasources_schema.go
@@ -0,0 +1,110 @@
+package connection_route_filter
+
+import (
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+)
+
+func dataSourceByUUIDSchema() map[string]*schema.Schema {
+ dsSchema := baseSchema()
+ dsSchema["connection_id"] = connectionIdSchema()
+ dsSchema["route_filter_id"] = routeFilterIdSchema()
+ return dsSchema
+}
+
+func dataSourceAllFiltersSchema() map[string]*schema.Schema {
+ return map[string]*schema.Schema{
+ "connection_id": connectionIdSchema(),
+ "pagination": {
+ Type: schema.TypeSet,
+ Computed: true,
+ Description: "Pagination details for the Data Source Search Request",
+ Elem: paginationSchema(),
+ },
+ "data": {
+ Type: schema.TypeList,
+ Computed: true,
+ Description: "The list of Rules attached to the given Route Filter Policy UUID",
+ Elem: &schema.Resource{
+ Schema: baseSchema(),
+ },
+ },
+ }
+}
+
+func baseSchema() map[string]*schema.Schema {
+ return map[string]*schema.Schema{
+ "direction": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "Direction of the filtering of the attached Route Filter Policy",
+ },
+ "type": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "Route Filter Type. One of [ \"BGP_IPv4_PREFIX_FILTER\", \"BGP_IPv6_PREFIX_FILTER\" ] ",
+ },
+ "href": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "URI to the attached Route Filter Policy on the Connection",
+ },
+ "uuid": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "Equinix Assigned ID for Route Filter Policy",
+ },
+ "attachment_status": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "Status of the Route Filter Policy attachment lifecycle",
+ },
+ }
+}
+
+func routeFilterIdSchema() *schema.Schema {
+ return &schema.Schema{
+ Type: schema.TypeString,
+ Required: true,
+ Description: "Equinix Assigned UUID of the Route Filter Policy to attach to the Equinix Connection",
+ }
+}
+
+func connectionIdSchema() *schema.Schema {
+ return &schema.Schema{
+ Type: schema.TypeString,
+ Required: true,
+ Description: "Equinix Assigned UUID of the Equinix Connection to attach the Route Filter Policy to",
+ }
+}
+
+func paginationSchema() *schema.Resource {
+ return &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "offset": {
+ Type: schema.TypeInt,
+ Computed: true,
+ Description: "The page offset for the pagination request. Index of the first element. Default is 0.",
+ },
+ "limit": {
+ Type: schema.TypeInt,
+ Computed: true,
+ Description: "Number of elements to be requested per page. Number must be between 1 and 100. Default is 20",
+ },
+ "total": {
+ Type: schema.TypeInt,
+ Computed: true,
+ Description: "Total number of elements returned.",
+ },
+ "next": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "URL relative to the last item in the response.",
+ },
+ "previous": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "URL relative to the first item in the response.",
+ },
+ },
+ }
+}
diff --git a/internal/resources/fabric/connection_route_filter/datasources_test.go b/internal/resources/fabric/connection_route_filter/datasources_test.go
new file mode 100644
index 000000000..2636823ca
--- /dev/null
+++ b/internal/resources/fabric/connection_route_filter/datasources_test.go
@@ -0,0 +1,3 @@
+package connection_route_filter_test
+
+// Tested in resource_test.go because of the heavy resource setup constraints
diff --git a/internal/resources/fabric/connection_route_filter/models.go b/internal/resources/fabric/connection_route_filter/models.go
new file mode 100644
index 000000000..f59edd980
--- /dev/null
+++ b/internal/resources/fabric/connection_route_filter/models.go
@@ -0,0 +1,67 @@
+package connection_route_filter
+
+import (
+ "github.com/equinix/equinix-sdk-go/services/fabricv4"
+ equinix_schema "github.com/equinix/terraform-provider-equinix/internal/schema"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+)
+
+func setConnectionRouteFilterMap(d *schema.ResourceData, connectionRouteFilter *fabricv4.ConnectionRouteFilterData) diag.Diagnostics {
+ diags := diag.Diagnostics{}
+ routeFilterMap := connectionRouteFilterResponseMap(connectionRouteFilter)
+ err := equinix_schema.SetMap(d, routeFilterMap)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ return diags
+}
+
+func setConnectionRouteFilterData(d *schema.ResourceData, connectionRouteFilters *fabricv4.GetAllConnectionRouteFiltersResponse) diag.Diagnostics {
+ diags := diag.Diagnostics{}
+ mappedRouteFilters := make([]map[string]interface{}, len(connectionRouteFilters.Data))
+ pagination := connectionRouteFilters.GetPagination()
+ if connectionRouteFilters.Data != nil {
+ for index, routeFilter := range connectionRouteFilters.Data {
+ mappedRouteFilters[index] = connectionRouteFilterResponseMap(&routeFilter)
+ }
+ } else {
+ mappedRouteFilters = nil
+ }
+ err := equinix_schema.SetMap(d, map[string]interface{}{
+ "data": mappedRouteFilters,
+ "pagination": paginationGoToTerraform(&pagination),
+ })
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ return diags
+}
+
+func connectionRouteFilterResponseMap(data *fabricv4.ConnectionRouteFilterData) map[string]interface{} {
+ connectionRouteFilterMap := make(map[string]interface{})
+ connectionRouteFilterMap["href"] = data.GetHref()
+ connectionRouteFilterMap["type"] = string(data.GetType())
+ connectionRouteFilterMap["uuid"] = data.GetUuid()
+ connectionRouteFilterMap["attachment_status"] = string(data.GetAttachmentStatus())
+ connectionRouteFilterMap["direction"] = string(data.GetDirection())
+
+ return connectionRouteFilterMap
+}
+
+func paginationGoToTerraform(pagination *fabricv4.Pagination) *schema.Set {
+ if pagination == nil {
+ return nil
+ }
+ mappedPagination := make(map[string]interface{})
+ mappedPagination["offset"] = int(pagination.GetOffset())
+ mappedPagination["limit"] = int(pagination.GetLimit())
+ mappedPagination["total"] = int(pagination.GetTotal())
+ mappedPagination["next"] = pagination.GetNext()
+ mappedPagination["previous"] = pagination.GetPrevious()
+
+ return schema.NewSet(
+ schema.HashResource(paginationSchema()),
+ []interface{}{mappedPagination},
+ )
+}
diff --git a/internal/resources/fabric/connection_route_filter/resource.go b/internal/resources/fabric/connection_route_filter/resource.go
new file mode 100644
index 000000000..b65e36c35
--- /dev/null
+++ b/internal/resources/fabric/connection_route_filter/resource.go
@@ -0,0 +1,203 @@
+package connection_route_filter
+
+import (
+ "context"
+ "log"
+ "strings"
+ "time"
+
+ "github.com/equinix/terraform-provider-equinix/internal/config"
+ equinix_errors "github.com/equinix/terraform-provider-equinix/internal/errors"
+
+ "github.com/equinix/equinix-sdk-go/services/fabricv4"
+
+ "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+)
+
+func Resource() *schema.Resource {
+ return &schema.Resource{
+ Timeouts: &schema.ResourceTimeout{
+ Create: schema.DefaultTimeout(10 * time.Minute),
+ Update: schema.DefaultTimeout(10 * time.Minute),
+ Delete: schema.DefaultTimeout(10 * time.Minute),
+ Read: schema.DefaultTimeout(10 * time.Minute),
+ },
+ ReadContext: resourceRead,
+ CreateContext: resourceCreate,
+ UpdateContext: resourceUpdate,
+ DeleteContext: resourceDelete,
+ Importer: &schema.ResourceImporter{
+ StateContext: schema.ImportStatePassthroughContext,
+ },
+ Schema: resourceSchema(),
+ Description: `Fabric V4 API compatible resource allows attachment of Route Filter Polices to Fabric Connections
+
+Additional Documentation:
+* Getting Started: https://docs.equinix.com/en-us/Content/Interconnection/FCR/FCR-route-filters.htm
+* API: https://developer.equinix.com/dev-docs/fabric/api-reference/fabric-v4-apis#route-filters`,
+ }
+}
+
+func resourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+ client := meta.(*config.Config).NewFabricClientForSDK(d)
+ connectionId := d.Get("connection_id").(string)
+ connectionRouteFilter, _, err := client.RouteFiltersApi.GetConnectionRouteFilterByUuid(ctx, d.Id(), connectionId).Execute()
+ if err != nil {
+ log.Printf("[WARN] Route Filter Policy %s not found on Connection %s, error %s", d.Id(), connectionId, err)
+ if !strings.Contains(err.Error(), "500") {
+ d.SetId("")
+ }
+ return diag.FromErr(equinix_errors.FormatFabricError(err))
+ }
+ d.SetId(connectionRouteFilter.GetUuid())
+ return setConnectionRouteFilterMap(d, connectionRouteFilter)
+}
+
+func resourceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+ client := meta.(*config.Config).NewFabricClientForSDK(d)
+ connectionId := d.Get("connection_id").(string)
+ routeFilterId := d.Get("route_filter_id").(string)
+ direction := d.Get("direction").(string)
+
+ start := time.Now()
+ routeFilter, _, err := client.RouteFiltersApi.
+ AttachConnectionRouteFilter(ctx, routeFilterId, connectionId).
+ ConnectionRouteFiltersBase(
+ fabricv4.ConnectionRouteFiltersBase{
+ Direction: fabricv4.ConnectionRouteFiltersBaseDirection(direction),
+ },
+ ).Execute()
+ if err != nil {
+ return diag.FromErr(equinix_errors.FormatFabricError(err))
+ }
+
+ if err = d.Set("connection_id", connectionId); err != nil {
+ return diag.Errorf("error setting connection_id to state %s", err)
+ }
+ d.SetId(routeFilter.GetUuid())
+
+ createTimeout := d.Timeout(schema.TimeoutCreate) - 30*time.Second - time.Since(start)
+ if err = waitForStability(connectionId, d.Id(), meta, d, ctx, createTimeout); err != nil {
+ return diag.Errorf("error waiting for route filter (%s) to be attached to connection (%s): %s", d.Id(), connectionId, err)
+ }
+
+ return resourceRead(ctx, d, meta)
+}
+
+func resourceUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+ client := meta.(*config.Config).NewFabricClientForSDK(d)
+ connectionId := d.Get("connection_id").(string)
+ routeFilterId := d.Get("route_filter_id").(string)
+ oldDirection, newDirection := d.GetChange("direction")
+ if oldDirection.(string) == newDirection.(string) {
+ return diag.Diagnostics{}
+ }
+
+ start := time.Now()
+ connectionRouteFilter, _, err := client.RouteFiltersApi.
+ AttachConnectionRouteFilter(ctx, routeFilterId, connectionId).
+ ConnectionRouteFiltersBase(
+ fabricv4.ConnectionRouteFiltersBase{
+ Direction: fabricv4.ConnectionRouteFiltersBaseDirection(newDirection.(string)),
+ },
+ ).Execute()
+ if err != nil {
+ return diag.FromErr(equinix_errors.FormatFabricError(err))
+ }
+
+ updateTimeout := d.Timeout(schema.TimeoutUpdate) - 30*time.Second - time.Since(start)
+ if err = waitForStability(routeFilterId, d.Id(), meta, d, ctx, updateTimeout); err != nil {
+ return diag.Errorf("error waiting for route filter policy (%s) on connection (%s) to be updated: %s", routeFilterId, connectionId, err)
+ }
+
+ return setConnectionRouteFilterMap(d, connectionRouteFilter)
+}
+
+func resourceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+ diags := diag.Diagnostics{}
+ client := meta.(*config.Config).NewFabricClientForSDK(d)
+ connectionId := d.Get("connection_id").(string)
+
+ start := time.Now()
+ _, _, err := client.RouteFiltersApi.DetachConnectionRouteFilter(ctx, d.Id(), connectionId).Execute()
+ if err != nil {
+ if genericError, ok := err.(*fabricv4.GenericOpenAPIError); ok {
+ if fabricErrs, ok := genericError.Model().([]fabricv4.Error); ok {
+ // EQ-3142509 = Connection already deleted
+ if equinix_errors.HasErrorCode(fabricErrs, "EQ-3142509") {
+ return diags
+ }
+ }
+ }
+ return diag.FromErr(equinix_errors.FormatFabricError(err))
+ }
+
+ deleteTimeout := d.Timeout(schema.TimeoutDelete) - 30*time.Second - time.Since(start)
+ if err = WaitForDeletion(connectionId, d.Id(), meta, d, ctx, deleteTimeout); err != nil {
+ return diag.Errorf("error waiting for route filter (%s) to be detached from connection (%s): %s", d.Id(), connectionId, err)
+ }
+ return diags
+}
+
+func waitForStability(connectionId, routeFilterId string, meta interface{}, d *schema.ResourceData, ctx context.Context, timeout time.Duration) error {
+ log.Printf("Waiting for route filter policy (%x) attachment to connection (%s) to be stable", connectionId, routeFilterId)
+ stateConf := &retry.StateChangeConf{
+ Pending: []string{
+ string(fabricv4.CONNECTIONROUTEFILTERDATAATTACHMENTSTATUS_ATTACHING),
+ },
+ Target: []string{
+ string(fabricv4.CONNECTIONROUTEFILTERDATAATTACHMENTSTATUS_ATTACHED),
+ string(fabricv4.CONNECTIONROUTEFILTERDATAATTACHMENTSTATUS_PENDING_BGP_CONFIGURATION),
+ },
+ Refresh: func() (interface{}, string, error) {
+ client := meta.(*config.Config).NewFabricClientForSDK(d)
+ connectionRouteFilter, _, err := client.RouteFiltersApi.GetConnectionRouteFilterByUuid(ctx, routeFilterId, connectionId).Execute()
+ if err != nil {
+ return "", "", equinix_errors.FormatFabricError(err)
+ }
+ return connectionRouteFilter, string(connectionRouteFilter.GetAttachmentStatus()), nil
+ },
+ Timeout: timeout,
+ Delay: 30 * time.Second,
+ MinTimeout: 30 * time.Second,
+ }
+
+ _, err := stateConf.WaitForStateContext(ctx)
+
+ return err
+}
+
+func WaitForDeletion(connectionId, routeFilterId string, meta interface{}, d *schema.ResourceData, ctx context.Context, timeout time.Duration) error {
+ log.Printf("Waiting for route filter policy (%s) to be detached from connection (%s)", routeFilterId, connectionId)
+ stateConf := &retry.StateChangeConf{
+ Pending: []string{
+ string(fabricv4.CONNECTIONROUTEFILTERDATAATTACHMENTSTATUS_ATTACHED),
+ string(fabricv4.CONNECTIONROUTEFILTERDATAATTACHMENTSTATUS_DETACHING),
+ string(fabricv4.CONNECTIONROUTEFILTERDATAATTACHMENTSTATUS_PENDING_BGP_CONFIGURATION),
+ },
+ Target: []string{
+ string(fabricv4.CONNECTIONROUTEFILTERDATAATTACHMENTSTATUS_DETACHED),
+ },
+ Refresh: func() (interface{}, string, error) {
+ client := meta.(*config.Config).NewFabricClientForSDK(d)
+ connectionRouteFilter, body, err := client.RouteFiltersApi.GetConnectionRouteFilterByUuid(ctx, routeFilterId, connectionId).Execute()
+ if err != nil {
+ if body.StatusCode >= 400 && body.StatusCode <= 499 {
+ // Already deleted resource
+ return connectionRouteFilter, string(fabricv4.ROUTEFILTERSTATE_DEPROVISIONED), nil
+ }
+ return "", "", equinix_errors.FormatFabricError(err)
+ }
+ return connectionRouteFilter, string(connectionRouteFilter.GetAttachmentStatus()), nil
+ },
+ Timeout: timeout,
+ Delay: 30 * time.Second,
+ MinTimeout: 30 * time.Second,
+ }
+
+ _, err := stateConf.WaitForStateContext(ctx)
+
+ return err
+}
diff --git a/internal/resources/fabric/connection_route_filter/resource_schema.go b/internal/resources/fabric/connection_route_filter/resource_schema.go
new file mode 100644
index 000000000..528628f7b
--- /dev/null
+++ b/internal/resources/fabric/connection_route_filter/resource_schema.go
@@ -0,0 +1,49 @@
+package connection_route_filter
+
+import (
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
+)
+
+func resourceSchema() map[string]*schema.Schema {
+ return map[string]*schema.Schema{
+ "connection_id": {
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ Description: "Equinix Assigned UUID of the Equinix Connection to attach the Route Filter Policy to",
+ },
+ "route_filter_id": {
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ Description: "Equinix Assigned UUID of the Route Filter Policy to attach to the Equinix Connection",
+ },
+ "direction": {
+ Type: schema.TypeString,
+ Required: true,
+ ValidateFunc: validation.StringInSlice([]string{"INBOUND", "OUTBOUND"}, false),
+ Description: "Direction of the filtering of the attached Route Filter Policy",
+ },
+ "type": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "Route Filter Type. One of [ \"BGP_IPv4_PREFIX_FILTER\", \"BGP_IPv6_PREFIX_FILTER\" ] ",
+ },
+ "href": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "URI to the attached Route Filter Policy on the Connection",
+ },
+ "uuid": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "Equinix Assigned ID for Route Filter Policy",
+ },
+ "attachment_status": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "Status of the Route Filter Policy attachment lifecycle",
+ },
+ }
+}
diff --git a/internal/resources/fabric/connection_route_filter/resource_test.go b/internal/resources/fabric/connection_route_filter/resource_test.go
new file mode 100644
index 000000000..59463ce4d
--- /dev/null
+++ b/internal/resources/fabric/connection_route_filter/resource_test.go
@@ -0,0 +1,196 @@
+package connection_route_filter_test
+
+import (
+ "context"
+ "fmt"
+ "testing"
+ "time"
+
+ "github.com/equinix/terraform-provider-equinix/internal/acceptance"
+ "github.com/equinix/terraform-provider-equinix/internal/fabric/testing_helpers"
+ "github.com/equinix/terraform-provider-equinix/internal/resources/fabric/connection_route_filter"
+
+ "github.com/equinix/equinix-sdk-go/services/fabricv4"
+
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "github.com/hashicorp/terraform-plugin-testing/terraform"
+)
+
+func TestAccFabricConnectionRouteFilter_PFCR(t *testing.T) {
+ ports := testing_helpers.GetFabricEnvPorts(t)
+ var portUuid string
+ if len(ports) > 0 {
+ portUuid = ports["pfcr"]["dot1q"][0].GetUuid()
+ }
+ resource.ParallelTest(t, resource.TestCase{
+ PreCheck: func() { acceptance.TestAccPreCheck(t); acceptance.TestAccPreCheckProviderConfigured(t) },
+ Providers: acceptance.TestAccProviders,
+ CheckDestroy: CheckConnectionRouteFilterDelete,
+ Steps: []resource.TestStep{
+ {
+ Config: testAccFabricConnectionRouteFilterConfig(portUuid),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttrSet("equinix_fabric_connection_route_filter.test", "id"),
+ resource.TestCheckResourceAttrSet("equinix_fabric_connection_route_filter.test", "connection_id"),
+ resource.TestCheckResourceAttrSet("equinix_fabric_connection_route_filter.test", "route_filter_id"),
+
+ resource.TestCheckResourceAttr(
+ "equinix_fabric_connection_route_filter.test", "direction", "INBOUND"),
+ resource.TestCheckResourceAttr(
+ "equinix_fabric_connection_route_filter.test", "type", "BGP_IPv4_PREFIX_FILTER"),
+ resource.TestCheckResourceAttr(
+ "equinix_fabric_connection_route_filter.test", "attachment_status", string(fabricv4.CONNECTIONROUTEFILTERDATAATTACHMENTSTATUS_PENDING_BGP_CONFIGURATION)),
+ resource.TestCheckResourceAttrSet("data.equinix_fabric_connection_route_filter.test", "id"),
+ resource.TestCheckResourceAttrSet("data.equinix_fabric_connection_route_filter.test", "connection_id"),
+ resource.TestCheckResourceAttrSet("data.equinix_fabric_connection_route_filter.test", "route_filter_id"),
+
+ resource.TestCheckResourceAttr(
+ "data.equinix_fabric_connection_route_filter.test", "direction", "INBOUND"),
+ resource.TestCheckResourceAttr(
+ "data.equinix_fabric_connection_route_filter.test", "type", "BGP_IPv4_PREFIX_FILTER"),
+ resource.TestCheckResourceAttr(
+ "data.equinix_fabric_connection_route_filter.test", "attachment_status", string(fabricv4.CONNECTIONROUTEFILTERDATAATTACHMENTSTATUS_PENDING_BGP_CONFIGURATION)),
+ resource.TestCheckResourceAttrSet("data.equinix_fabric_connection_route_filters.test", "id"),
+ resource.TestCheckResourceAttrSet("data.equinix_fabric_connection_route_filters.test", "connection_id"),
+ resource.TestCheckResourceAttrSet("data.equinix_fabric_connection_route_filters.test", "data.0.uuid"),
+
+ resource.TestCheckResourceAttr(
+ "data.equinix_fabric_connection_route_filters.test", "data.0.direction", "INBOUND"),
+ resource.TestCheckResourceAttr(
+ "data.equinix_fabric_connection_route_filters.test", "data.0.type", "BGP_IPv4_PREFIX_FILTER"),
+ resource.TestCheckResourceAttr(
+ "data.equinix_fabric_connection_route_filters.test", "data.0.attachment_status", string(fabricv4.CONNECTIONROUTEFILTERDATAATTACHMENTSTATUS_PENDING_BGP_CONFIGURATION)),
+ ),
+ ExpectNonEmptyPlan: false,
+ },
+ },
+ })
+
+}
+
+func testAccFabricConnectionRouteFilterConfig(portUuid string) string {
+ return fmt.Sprintf(`
+ resource "equinix_fabric_cloud_router" "test" {
+ type = "XF_ROUTER"
+ name = "RF_CR_PFCR"
+ location {
+ metro_code = "DC"
+ }
+ package {
+ code = "STANDARD"
+ }
+ order {
+ purchase_order_number = "1-234567"
+ }
+ notifications {
+ type = "ALL"
+ emails = [
+ "test@equinix.com",
+ "test1@equinix.com"
+ ]
+ }
+ project {
+ project_id = "291639000636552"
+ }
+ account {
+ account_number = 201257
+ }
+ }
+
+ resource "equinix_fabric_connection" "test" {
+ type = "IP_VC"
+ name = "RF_CR_Connection_PFCR"
+ notifications {
+ type = "ALL"
+ emails = ["test@equinix.com","test1@equinix.com"]
+ }
+ order {
+ purchase_order_number = "123485"
+ }
+ bandwidth = 50
+ redundancy {
+ priority= "PRIMARY"
+ }
+ a_side {
+ access_point {
+ type = "CLOUD_ROUTER"
+ router {
+ uuid = equinix_fabric_cloud_router.test.id
+ }
+ }
+ }
+ project {
+ project_id = "291639000636552"
+ }
+ z_side {
+ access_point {
+ type = "COLO"
+ port{
+ uuid = "%s"
+ }
+ link_protocol {
+ type= "DOT1Q"
+ vlan_tag= 2571
+ }
+ location {
+ metro_code = "DC"
+ }
+ }
+ }
+ }
+
+ resource "equinix_fabric_route_filter" "test" {
+ name = "rf_test_PFCR"
+ project {
+ project_id = "291639000636552"
+ }
+ type = "BGP_IPv4_PREFIX_FILTER"
+ description = "Route Filter Policy for X Purpose"
+ }
+
+ resource "equinix_fabric_route_filter_rule" "test" {
+ route_filter_id = equinix_fabric_route_filter.test.id
+ name = "RF_Rule_PFCR"
+ prefix = "192.168.0.0/24"
+ prefix_match = "exact"
+ description = "Route Filter Rule for X Purpose"
+ }
+
+ resource "equinix_fabric_connection_route_filter" "test" {
+ depends_on = [ equinix_fabric_route_filter_rule.test ]
+ connection_id = equinix_fabric_connection.test.id
+ route_filter_id = equinix_fabric_route_filter.test.id
+ direction = "INBOUND"
+ }
+
+ data "equinix_fabric_connection_route_filter" "test" {
+ depends_on = [ equinix_fabric_connection_route_filter.test ]
+ connection_id = equinix_fabric_connection.test.id
+ route_filter_id = equinix_fabric_route_filter.test.id
+ }
+
+ data "equinix_fabric_connection_route_filters" "test" {
+ depends_on = [ equinix_fabric_connection_route_filter.test ]
+ connection_id = equinix_fabric_connection.test.id
+ }
+
+ `, portUuid)
+}
+
+func CheckConnectionRouteFilterDelete(s *terraform.State) error {
+ ctx := context.Background()
+ for _, rs := range s.RootModule().Resources {
+ if rs.Type != "equinix_fabric_connection_route_filter" {
+ continue
+ }
+
+ connectionId := rs.Primary.Attributes["connection_id"]
+
+ err := connection_route_filter.WaitForDeletion(connectionId, rs.Primary.ID, acceptance.TestAccProvider.Meta(), &schema.ResourceData{}, ctx, 10*time.Minute)
+ if err != nil {
+ return fmt.Errorf("API call failed while waiting for resource deletion")
+ }
+ }
+ return nil
+}