From b7ca341aa27328852f250ff85c650b512a9c1c50 Mon Sep 17 00:00:00 2001 From: srushti-patl Date: Tue, 18 Feb 2025 14:40:26 -0800 Subject: [PATCH 1/4] feat: Adding Route Aggregation Rule and Connection Route Aggregation Data Sources and Resources --- .../fabric_connection_route_aggregation.md | 50 +++ .../fabric_connection_route_aggregations.md | 72 +++++ .../fabric_route_aggregation_rule.md | 93 ++++++ .../fabric_route_aggregation_rules.md | 118 +++++++ .../fabric_connection_route_aggregation.md | 62 ++++ .../fabric_route_aggregation_rule.md | 104 +++++++ .../data-source.tf | 20 ++ .../data-source.tf | 15 + .../data-source.tf | 24 ++ .../data-source.tf | 23 ++ .../resource.tf | 20 ++ .../resource.tf | 26 ++ internal/provider/services/fabric.go | 8 + ...asource_all_connectionRouteAggregations.go | 57 ++++ ...asource_by_connectionRouteAggregationId.go | 59 ++++ .../datasource_schema.go | 111 +++++++ .../datasource_test.go | 59 ++++ .../connection_route_aggregation/models.go | 138 +++++++++ .../connection_route_aggregation/resource.go | 212 +++++++++++++ .../resource_schema.go | 46 +++ .../resource_test.go | 143 +++++++++ .../datasource_all_routeAggregationRules.go | 69 +++++ .../datasource_by_routeAggregationRuleId.go | 60 ++++ .../datasource_schema.go | 191 ++++++++++++ .../route_aggregation_rule/datasource_test.go | 78 +++++ .../fabric/route_aggregation_rule/models.go | 210 +++++++++++++ .../fabric/route_aggregation_rule/resource.go | 288 ++++++++++++++++++ .../route_aggregation_rule/resource_schema.go | 138 +++++++++ .../route_aggregation_rule/resource_test.go | 109 +++++++ 29 files changed, 2603 insertions(+) create mode 100644 docs/data-sources/fabric_connection_route_aggregation.md create mode 100644 docs/data-sources/fabric_connection_route_aggregations.md create mode 100644 docs/data-sources/fabric_route_aggregation_rule.md create mode 100644 docs/data-sources/fabric_route_aggregation_rules.md create mode 100644 docs/resources/fabric_connection_route_aggregation.md create mode 100644 docs/resources/fabric_route_aggregation_rule.md create mode 100644 examples/data-sources/equinix_fabric_connection_route_aggregation/data-source.tf create mode 100644 examples/data-sources/equinix_fabric_connection_route_aggregations/data-source.tf create mode 100644 examples/data-sources/equinix_fabric_route_aggregation_rule/data-source.tf create mode 100644 examples/data-sources/equinix_fabric_route_aggregation_rules/data-source.tf create mode 100644 examples/resources/equinix_fabric_connection_route_aggregation/resource.tf create mode 100644 examples/resources/equinix_fabric_route_aggregation_rule/resource.tf create mode 100644 internal/resources/fabric/connection_route_aggregation/datasource_all_connectionRouteAggregations.go create mode 100644 internal/resources/fabric/connection_route_aggregation/datasource_by_connectionRouteAggregationId.go create mode 100644 internal/resources/fabric/connection_route_aggregation/datasource_schema.go create mode 100644 internal/resources/fabric/connection_route_aggregation/datasource_test.go create mode 100644 internal/resources/fabric/connection_route_aggregation/models.go create mode 100644 internal/resources/fabric/connection_route_aggregation/resource.go create mode 100644 internal/resources/fabric/connection_route_aggregation/resource_schema.go create mode 100644 internal/resources/fabric/connection_route_aggregation/resource_test.go create mode 100644 internal/resources/fabric/route_aggregation_rule/datasource_all_routeAggregationRules.go create mode 100644 internal/resources/fabric/route_aggregation_rule/datasource_by_routeAggregationRuleId.go create mode 100644 internal/resources/fabric/route_aggregation_rule/datasource_schema.go create mode 100644 internal/resources/fabric/route_aggregation_rule/datasource_test.go create mode 100644 internal/resources/fabric/route_aggregation_rule/models.go create mode 100644 internal/resources/fabric/route_aggregation_rule/resource.go create mode 100644 internal/resources/fabric/route_aggregation_rule/resource_schema.go create mode 100644 internal/resources/fabric/route_aggregation_rule/resource_test.go diff --git a/docs/data-sources/fabric_connection_route_aggregation.md b/docs/data-sources/fabric_connection_route_aggregation.md new file mode 100644 index 000000000..0b62e2536 --- /dev/null +++ b/docs/data-sources/fabric_connection_route_aggregation.md @@ -0,0 +1,50 @@ +--- +subcategory: "Fabric" +--- + +# equinix_fabric_connection_route_aggregation (Data Source) + +Fabric V4 API compatible data resource that allow user to fetch Equinix Fabric Connection Route Aggregation by UUID +Additional Documentation: +* API: https://developer.equinix.com/catalog/fabricv4#tag/Route-Aggregations + +## Example Usage + +```terraform +data "equinix_fabric_connection_route_aggregation" "attached_policy" { + route_aggregation_id = "" + connection_id = "" +} + +output "connection_route_Aggregation_id" { + value = data.equinix_fabric_connection_route_aggregation.attached_policy.id +} + +output "connection_route_aggregation_connection_id" { + value = data.equinix_fabric_connection_route_aggregation.attached_policy.connection_id +} + +output "connection_route_aggregation_type" { + value = data.equinix_fabric_connection_route_aggregation.attached_policy.type +} + +output "connection_route_aggregation_attachment_status" { + value = data.equinix_fabric_connection_route_aggregation.attached_policy.attachment_status +} +``` + + +## Schema + +### Required + +- `connection_id` (String) The uuid of the connection this data source should retrieve +- `route_aggregation_id` (String) The uuid of the route aggregation this data source should retrieve + +### Read-Only + +- `attachment_status` (String) Status of the Route Aggregation Policy attachment lifecycle +- `href` (String) URI to the attached Route Aggregation Policy on the Connection +- `id` (String) The unique identifier of the resource +- `type` (String) Route Aggregation Type. One of ["BGP_IPv4_PREFIX_AGGREGATION"] +- `uuid` (String) Equinix Assigned ID for Route Aggregation Policy diff --git a/docs/data-sources/fabric_connection_route_aggregations.md b/docs/data-sources/fabric_connection_route_aggregations.md new file mode 100644 index 000000000..cea4051fc --- /dev/null +++ b/docs/data-sources/fabric_connection_route_aggregations.md @@ -0,0 +1,72 @@ +--- +subcategory: "Fabric" +--- + +# equinix_fabric_connection_route_aggregations (Data Source) + +Fabric V4 API compatible data resource that allow user to fetch Equinix Fabric Connection Route Aggregations with pagination details +Additional Documentation: +* API: https://developer.equinix.com/catalog/fabricv4#tag/Route-Aggregations + +## Example Usage + +```terraform +data "equinix_fabric_connection_route_aggregations" "attached_policies" { + connection_id = "connection_id" +} + +output "connection_first_route_Aggregation_uuid" { + value = data.equinix_fabric_connection_route_aggregations.attached_policies.data.0.uuid +} + +output "connection_first_route_aggregation_type" { + value = data.equinix_fabric_connection_route_aggregations.attached_policies.data.0.type +} + +output "connection_first_route_aggregation_attachment_status" { + value = data.equinix_fabric_connection_route_aggregations.attached_policies.data.0.attachment_status +} +``` + + +## Schema + +### Required + +- `connection_id` (String) The uuid of the connection this data source should retrieve + +### Optional + +- `pagination` (Attributes) Pagination details for the returned connection route aggregations list (see [below for nested schema](#nestedatt--pagination)) + +### Read-Only + +- `data` (Attributes List) Returned list of connection route aggregation objects (see [below for nested schema](#nestedatt--data)) +- `id` (String) The unique identifier of the resource + + +### Nested Schema for `pagination` + +Optional: + +- `limit` (Number) Maximum number of search results returned per page. Number must be between 1 and 100, and the default is 20 +- `next` (String) The URL relative to the next item in the response +- `offset` (Number) Index of the first item returned in the response. The default is 0 +- `previous` (String) The URL relative to the previous item in the response +- `total` (Number) The total number of connection route aggregations available to the user making the request + + + +### Nested Schema for `data` + +Required: + +- `connection_id` (String) UUID of the Connection to attach this Route Aggregation to +- `route_aggregation_id` (String) UUID of the Route Aggregation to attach this Connection to + +Read-Only: + +- `attachment_status` (String) Status of the Route Aggregation Policy attachment lifecycle +- `href` (String) URI to the attached Route Aggregation Policy on the Connection +- `type` (String) Route Aggregation Type. One of ["BGP_IPv4_PREFIX_AGGREGATION"] +- `uuid` (String) Equinix Assigned ID for Route Aggregation Policy diff --git a/docs/data-sources/fabric_route_aggregation_rule.md b/docs/data-sources/fabric_route_aggregation_rule.md new file mode 100644 index 000000000..188b15508 --- /dev/null +++ b/docs/data-sources/fabric_route_aggregation_rule.md @@ -0,0 +1,93 @@ +--- +subcategory: "Fabric" +--- + +# equinix_fabric_route_aggregation_rule (Data Source) + +Fabric V4 API compatible data resource that allow user to fetch Equinix Fabric Route Aggregation Rule by UUID +Additional Documentation: +* API: https://developer.equinix.com/catalog/fabricv4#tag/Route-Aggregations + +## Example Usage + +```terraform +data "equinix_fabric_route_aggregation_rule" "ra_rule" { + route_aggregation_id = "" + route_aggregation_rule_id = "" +} + +output "route_aggregation_rule_name" { + value = data.equinix_fabric_route_aggregation_rule.ra_rule.name +} + +output "route_aggregation_rule_description" { + value = data.equinix_fabric_route_aggregation_rule.ra_rule.description +} + +output "route_aggregation_rule_type" { + value = data.equinix_fabric_route_aggregation_rule.ra_rule.type +} + +output "route_aggregation_rule_prefix" { + value = data.equinix_fabric_route_aggregation_rule.ra_rule.prefix +} + +output "route_aggregation_rule_state" { + value = data.equinix_fabric_route_aggregation_rule.ra_rule.state +} +``` + + +## Schema + +### Required + +- `route_aggregation_id` (String) The uuid of the route aggregation this data source should retrieve +- `route_aggregation_rule_id` (String) The uuid of the route aggregation rule this data source should retrieve + +### Optional + +- `description` (String) Customer-provided route aggregation rule description + +### Read-Only + +- `change` (Attributes) Current state of latest route aggregation rule change (see [below for nested schema](#nestedatt--change)) +- `change_log` (Attributes) Details of the last change on the stream resource (see [below for nested schema](#nestedatt--change_log)) +- `href` (String) Equinix auto generated URI to the route aggregation rule resource +- `id` (String) The unique identifier of the resource +- `name` (String) Customer provided name of the route aggregation rule +- `prefix` (String) Customer-provided route aggregation rule prefix +- `state` (String) Value representing provisioning status for the route aggregation rule resource +- `type` (String) Equinix defined Route Aggregation Type; BGP_IPv4_PREFIX_AGGREGATION, BGP_IPv6_PREFIX_AGGREGATION +- `uuid` (String) Equinix-assigned unique id for the route aggregation rule resource + + +### Nested Schema for `change` + +Required: + +- `type` (String) Equinix defined Route Aggregation Change Type +- `uuid` (String) Equinix-assigned unique id for a change + +Read-Only: + +- `href` (String) Equinix auto generated URI to the route aggregation change + + + +### Nested Schema for `change_log` + +Read-Only: + +- `created_by` (String) User name of creator of the stream resource +- `created_by_email` (String) Email of creator of the stream resource +- `created_by_full_name` (String) Legal name of creator of the stream resource +- `created_date_time` (String) Creation time of the stream resource +- `deleted_by` (String) User name of deleter of the stream resource +- `deleted_by_email` (String) Email of deleter of the stream resource +- `deleted_by_full_name` (String) Legal name of deleter of the stream resource +- `deleted_date_time` (String) Deletion time of the stream resource +- `updated_by` (String) User name of last updater of the stream resource +- `updated_by_email` (String) Email of last updater of the stream resource +- `updated_by_full_name` (String) Legal name of last updater of the stream resource +- `updated_date_time` (String) Last update time of the stream resource diff --git a/docs/data-sources/fabric_route_aggregation_rules.md b/docs/data-sources/fabric_route_aggregation_rules.md new file mode 100644 index 000000000..eab838713 --- /dev/null +++ b/docs/data-sources/fabric_route_aggregation_rules.md @@ -0,0 +1,118 @@ +--- +subcategory: "Fabric" +--- + +# equinix_fabric_route_aggregation_rules (Data Source) + +Fabric V4 API compatible data resource that allow user to fetch Equinix Fabric Route Aggregation Rules with pagination details +Additional Documentation: +* API: https://developer.equinix.com/catalog/fabricv4#tag/Route-Aggregations + +## Example Usage + +```terraform +data "equinix_fabric_route_aggregation_rules" "ra_rules" { + route_aggregation_id = "" + pagination = { + limit = 2 + offset = 1 + } +} + +output "route_aggregation_rule_name" { + value = data.equinix_fabric_route_aggregation_rules.ra_rules.data.0.name +} + +output "route_aggregation_rule_description" { + value = data.equinix_fabric_route_aggregation_rules.ra_rules.data.0.description +} + +output "route_aggregation_rule_prefix" { + value = data.equinix_fabric_route_aggregation_rules.ra_rules.data.0.prefix +} + +output "route_aggregation_rule_state" { + value = data.equinix_fabric_route_aggregation_rules.ra_rules.data.0.state +} +``` + + +## Schema + +### Required + +- `route_aggregation_id` (String) The uuid of the route aggregation rule this data source should retrieve + +### Optional + +- `pagination` (Attributes) Pagination details for the returned route aggregation rules list (see [below for nested schema](#nestedatt--pagination)) + +### Read-Only + +- `data` (Attributes List) Returned list of route aggregation rule objects (see [below for nested schema](#nestedatt--data)) +- `id` (String) The unique identifier of the resource + + +### Nested Schema for `pagination` + +Optional: + +- `limit` (Number) Maximum number of search results returned per page. Number must be between 1 and 100, and the default is 20 +- `offset` (Number) Index of the first item returned in the response. The default is 0 + +Read-Only: + +- `next` (String) The URL relative to the next item in the response +- `previous` (String) The URL relative to the previous item in the response +- `total` (Number) The total number of route agrgegation rules available to the user making the request + + + +### Nested Schema for `data` + +Optional: + +- `description` (String) Customer-provided route aggregation rule description + +Read-Only: + +- `change` (Attributes) Current state of latest route aggregation rule change (see [below for nested schema](#nestedatt--data--change)) +- `change_log` (Attributes) Details of the last change on the stream resource (see [below for nested schema](#nestedatt--data--change_log)) +- `href` (String) Equinix auto generated URI to the route aggregation rule resource +- `name` (String) Customer provided name of the route aggregation rule +- `prefix` (String) Customer-provided route aggregation rule prefix +- `route_aggregation_id` (String) UUID of the Route Aggregation to apply this Rule to +- `state` (String) Value representing provisioning status for the route aggregation rule resource +- `type` (String) Equinix defined Route Aggregation Type; BGP_IPv4_PREFIX_AGGREGATION, BGP_IPv6_PREFIX_AGGREGATION +- `uuid` (String) Equinix-assigned unique id for the route aggregation rule resource + + +### Nested Schema for `data.change` + +Required: + +- `type` (String) Equinix defined Route Aggregation Change Type +- `uuid` (String) Equinix-assigned unique id for a change + +Read-Only: + +- `href` (String) Equinix auto generated URI to the route aggregation change + + + +### Nested Schema for `data.change_log` + +Read-Only: + +- `created_by` (String) User name of creator of the stream resource +- `created_by_email` (String) Email of creator of the stream resource +- `created_by_full_name` (String) Legal name of creator of the stream resource +- `created_date_time` (String) Creation time of the stream resource +- `deleted_by` (String) User name of deleter of the stream resource +- `deleted_by_email` (String) Email of deleter of the stream resource +- `deleted_by_full_name` (String) Legal name of deleter of the stream resource +- `deleted_date_time` (String) Deletion time of the stream resource +- `updated_by` (String) User name of last updater of the stream resource +- `updated_by_email` (String) Email of last updater of the stream resource +- `updated_by_full_name` (String) Legal name of last updater of the stream resource +- `updated_date_time` (String) Last update time of the stream resource diff --git a/docs/resources/fabric_connection_route_aggregation.md b/docs/resources/fabric_connection_route_aggregation.md new file mode 100644 index 000000000..235f26f69 --- /dev/null +++ b/docs/resources/fabric_connection_route_aggregation.md @@ -0,0 +1,62 @@ +--- +subcategory: "Fabric" +--- + +# equinix_fabric_connection_route_aggregation (Resource) + + + +## Example Usage + +```terraform +resource "equinix_fabric_connection_route_aggregation" "policy_attachment" { + route_aggregation_id = "" + connection_id = "" +} + +output "connection_route_Aggregation_id" { + value = equinix_fabric_connection_route_aggregation.policy_attachment.id +} + +output "connection_route_aggregation_connection_id" { + value = equinix_fabric_connection_route_aggregation.policy_attachment.connection_id +} + +output "connection_route_aggregation_type" { + value = equinix_fabric_connection_route_aggregation.policy_attachment.type +} + +output "connection_route_aggregation_attachment_status" { + value = equinix_fabric_connection_route_aggregation.policy_attachment.attachment_status +} +``` + + +## Schema + +### Required + +- `connection_id` (String) Equinix Assigned UUID of the Equinix Connection to attach the Route Aggregation Policy to +- `route_aggregation_id` (String) UUID of the Route Aggregation to apply this Rule to + +### Optional + +- `timeouts` (Attributes) (see [below for nested schema](#nestedatt--timeouts)) + +### Read-Only + +- `attachment_status` (String) Status of the Route Aggregation Policy attachment lifecycle +- `href` (String) URI to the attached Route Aggregation Policy on the Connection +- `id` (String) The unique identifier of the resource +- `type` (String) Route Aggregation Type. One of ["BGP_IPv4_PREFIX_AGGREGATION"] +- `uuid` (String) Equinix Assigned ID for Route Aggregation Policy + + +### Nested Schema for `timeouts` + +Optional: + +- `create` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). +- `delete` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). Setting a timeout for a Delete operation is only applicable if changes are saved into state before the destroy operation occurs. +- `read` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). Read operations occur during any refresh or planning operation when refresh is enabled. +- `update` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). diff --git a/docs/resources/fabric_route_aggregation_rule.md b/docs/resources/fabric_route_aggregation_rule.md new file mode 100644 index 000000000..864e1d7ed --- /dev/null +++ b/docs/resources/fabric_route_aggregation_rule.md @@ -0,0 +1,104 @@ +--- +subcategory: "Fabric" +--- + +# equinix_fabric_route_aggregation_rule (Resource) + + + +## Example Usage + +```terraform +resource "equinix_fabric_route_aggregation_rule" "ra_rule" { + route_aggregation_id = "" + name = "ra-rule-test" + description = "Route aggregation rule" + prefix = "192.168.0.0/24" +} + +output "route_aggregation_rule_name" { + value = equinix_fabric_route_aggregation_rule.ra_rule.name +} + +output "route_aggregation_rule_description" { + value = equinix_fabric_route_aggregation_rule.ra_rule.description +} + +output "route_aggregation_rule_type" { + value = equinix_fabric_route_aggregation_rule.ra_rule.type +} + +output "route_aggregation_rule_prefix" { + value = equinix_fabric_route_aggregation_rule.ra_rule.prefix +} + +output "route_aggregation_rule_state" { + value = equinix_fabric_route_aggregation_rule.ra_rule.state +} +``` + + +## Schema + +### Required + +- `name` (String) Customer provided name of the route aggregation rule +- `prefix` (String) Customer-provided route aggregation rule prefix +- `route_aggregation_id` (String) UUID of the Route Aggregation to apply this Rule to + +### Optional + +- `description` (String) Customer-provided route aggregation rule description +- `timeouts` (Attributes) (see [below for nested schema](#nestedatt--timeouts)) + +### Read-Only + +- `change` (Attributes) Current state of latest route aggregation rule change (see [below for nested schema](#nestedatt--change)) +- `change_log` (Attributes) Details of the last change on the stream resource (see [below for nested schema](#nestedatt--change_log)) +- `href` (String) Equinix auto generated URI to the route aggregation rule resource +- `id` (String) The unique identifier of the resource +- `state` (String) Value representing provisioning status for the route aggregation rule resource +- `type` (String) Equinix defined Route Aggregation Type; BGP_IPv4_PREFIX_AGGREGATION, BGP_IPv6_PREFIX_AGGREGATION +- `uuid` (String) Equinix-assigned unique id for the route aggregation rule resource + + +### Nested Schema for `timeouts` + +Optional: + +- `create` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). +- `delete` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). Setting a timeout for a Delete operation is only applicable if changes are saved into state before the destroy operation occurs. +- `read` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). Read operations occur during any refresh or planning operation when refresh is enabled. +- `update` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). + + + +### Nested Schema for `change` + +Required: + +- `type` (String) Equinix defined Route Aggregation Change Type +- `uuid` (String) Equinix-assigned unique id for a change + +Read-Only: + +- `href` (String) Equinix auto generated URI to the route aggregation change + + + +### Nested Schema for `change_log` + +Read-Only: + +- `created_by` (String) User name of creator of the stream resource +- `created_by_email` (String) Email of creator of the stream resource +- `created_by_full_name` (String) Legal name of creator of the stream resource +- `created_date_time` (String) Creation time of the stream resource +- `deleted_by` (String) User name of deleter of the stream resource +- `deleted_by_email` (String) Email of deleter of the stream resource +- `deleted_by_full_name` (String) Legal name of deleter of the stream resource +- `deleted_date_time` (String) Deletion time of the stream resource +- `updated_by` (String) User name of last updater of the stream resource +- `updated_by_email` (String) Email of last updater of the stream resource +- `updated_by_full_name` (String) Legal name of last updater of the stream resource +- `updated_date_time` (String) Last update time of the stream resource diff --git a/examples/data-sources/equinix_fabric_connection_route_aggregation/data-source.tf b/examples/data-sources/equinix_fabric_connection_route_aggregation/data-source.tf new file mode 100644 index 000000000..09deaa7b1 --- /dev/null +++ b/examples/data-sources/equinix_fabric_connection_route_aggregation/data-source.tf @@ -0,0 +1,20 @@ +data "equinix_fabric_connection_route_aggregation" "attached_policy" { + route_aggregation_id = "" + connection_id = "" +} + +output "connection_route_Aggregation_id" { + value = data.equinix_fabric_connection_route_aggregation.attached_policy.id +} + +output "connection_route_aggregation_connection_id" { + value = data.equinix_fabric_connection_route_aggregation.attached_policy.connection_id +} + +output "connection_route_aggregation_type" { + value = data.equinix_fabric_connection_route_aggregation.attached_policy.type +} + +output "connection_route_aggregation_attachment_status" { + value = data.equinix_fabric_connection_route_aggregation.attached_policy.attachment_status +} \ No newline at end of file diff --git a/examples/data-sources/equinix_fabric_connection_route_aggregations/data-source.tf b/examples/data-sources/equinix_fabric_connection_route_aggregations/data-source.tf new file mode 100644 index 000000000..d4a1851d3 --- /dev/null +++ b/examples/data-sources/equinix_fabric_connection_route_aggregations/data-source.tf @@ -0,0 +1,15 @@ +data "equinix_fabric_connection_route_aggregations" "attached_policies" { + connection_id = "connection_id" +} + +output "connection_first_route_Aggregation_uuid" { + value = data.equinix_fabric_connection_route_aggregations.attached_policies.data.0.uuid +} + +output "connection_first_route_aggregation_type" { + value = data.equinix_fabric_connection_route_aggregations.attached_policies.data.0.type +} + +output "connection_first_route_aggregation_attachment_status" { + value = data.equinix_fabric_connection_route_aggregations.attached_policies.data.0.attachment_status +} \ No newline at end of file diff --git a/examples/data-sources/equinix_fabric_route_aggregation_rule/data-source.tf b/examples/data-sources/equinix_fabric_route_aggregation_rule/data-source.tf new file mode 100644 index 000000000..9a42149d6 --- /dev/null +++ b/examples/data-sources/equinix_fabric_route_aggregation_rule/data-source.tf @@ -0,0 +1,24 @@ +data "equinix_fabric_route_aggregation_rule" "ra_rule" { + route_aggregation_id = "" + route_aggregation_rule_id = "" +} + +output "route_aggregation_rule_name" { + value = data.equinix_fabric_route_aggregation_rule.ra_rule.name +} + +output "route_aggregation_rule_description" { + value = data.equinix_fabric_route_aggregation_rule.ra_rule.description +} + +output "route_aggregation_rule_type" { + value = data.equinix_fabric_route_aggregation_rule.ra_rule.type +} + +output "route_aggregation_rule_prefix" { + value = data.equinix_fabric_route_aggregation_rule.ra_rule.prefix +} + +output "route_aggregation_rule_state" { + value = data.equinix_fabric_route_aggregation_rule.ra_rule.state +} \ No newline at end of file diff --git a/examples/data-sources/equinix_fabric_route_aggregation_rules/data-source.tf b/examples/data-sources/equinix_fabric_route_aggregation_rules/data-source.tf new file mode 100644 index 000000000..00486ddb1 --- /dev/null +++ b/examples/data-sources/equinix_fabric_route_aggregation_rules/data-source.tf @@ -0,0 +1,23 @@ +data "equinix_fabric_route_aggregation_rules" "ra_rules" { + route_aggregation_id = "" + pagination = { + limit = 2 + offset = 1 + } +} + +output "route_aggregation_rule_name" { + value = data.equinix_fabric_route_aggregation_rules.ra_rules.data.0.name +} + +output "route_aggregation_rule_description" { + value = data.equinix_fabric_route_aggregation_rules.ra_rules.data.0.description +} + +output "route_aggregation_rule_prefix" { + value = data.equinix_fabric_route_aggregation_rules.ra_rules.data.0.prefix +} + +output "route_aggregation_rule_state" { + value = data.equinix_fabric_route_aggregation_rules.ra_rules.data.0.state +} \ No newline at end of file diff --git a/examples/resources/equinix_fabric_connection_route_aggregation/resource.tf b/examples/resources/equinix_fabric_connection_route_aggregation/resource.tf new file mode 100644 index 000000000..11b300b86 --- /dev/null +++ b/examples/resources/equinix_fabric_connection_route_aggregation/resource.tf @@ -0,0 +1,20 @@ +resource "equinix_fabric_connection_route_aggregation" "policy_attachment" { + route_aggregation_id = "" + connection_id = "" +} + +output "connection_route_Aggregation_id" { + value = equinix_fabric_connection_route_aggregation.policy_attachment.id +} + +output "connection_route_aggregation_connection_id" { + value = equinix_fabric_connection_route_aggregation.policy_attachment.connection_id +} + +output "connection_route_aggregation_type" { + value = equinix_fabric_connection_route_aggregation.policy_attachment.type +} + +output "connection_route_aggregation_attachment_status" { + value = equinix_fabric_connection_route_aggregation.policy_attachment.attachment_status +} \ No newline at end of file diff --git a/examples/resources/equinix_fabric_route_aggregation_rule/resource.tf b/examples/resources/equinix_fabric_route_aggregation_rule/resource.tf new file mode 100644 index 000000000..7bdc56c6d --- /dev/null +++ b/examples/resources/equinix_fabric_route_aggregation_rule/resource.tf @@ -0,0 +1,26 @@ +resource "equinix_fabric_route_aggregation_rule" "ra_rule" { + route_aggregation_id = "" + name = "ra-rule-test" + description = "Route aggregation rule" + prefix = "192.168.0.0/24" +} + +output "route_aggregation_rule_name" { + value = equinix_fabric_route_aggregation_rule.ra_rule.name +} + +output "route_aggregation_rule_description" { + value = equinix_fabric_route_aggregation_rule.ra_rule.description +} + +output "route_aggregation_rule_type" { + value = equinix_fabric_route_aggregation_rule.ra_rule.type +} + +output "route_aggregation_rule_prefix" { + value = equinix_fabric_route_aggregation_rule.ra_rule.prefix +} + +output "route_aggregation_rule_state" { + value = equinix_fabric_route_aggregation_rule.ra_rule.state +} diff --git a/internal/provider/services/fabric.go b/internal/provider/services/fabric.go index d89eadf6c..b420821d4 100644 --- a/internal/provider/services/fabric.go +++ b/internal/provider/services/fabric.go @@ -1,6 +1,8 @@ package services import ( + "github.com/equinix/terraform-provider-equinix/internal/resources/fabric/connection_route_aggregation" + "github.com/equinix/terraform-provider-equinix/internal/resources/fabric/route_aggregation_rule" "github.com/equinix/terraform-provider-equinix/internal/resources/fabric/stream" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -9,6 +11,8 @@ import ( func FabricResources() []func() resource.Resource { return []func() resource.Resource{ stream.NewResource, + route_aggregation_rule.NewResource, + connection_route_aggregation.NewResource, } } @@ -16,5 +20,9 @@ func FabricDatasources() []func() datasource.DataSource { return []func() datasource.DataSource{ stream.NewDataSourceByStreamID, stream.NewDataSourceAllStreams, + route_aggregation_rule.NewDataSourceByRouteAggregationRuleID, + route_aggregation_rule.NewDataSourceAllRouteAggregationRule, + connection_route_aggregation.NewDataSourceByConnectionRouteAggregationID, + connection_route_aggregation.NewDataSourceAllConnectionRouteAggregations, } } diff --git a/internal/resources/fabric/connection_route_aggregation/datasource_all_connectionRouteAggregations.go b/internal/resources/fabric/connection_route_aggregation/datasource_all_connectionRouteAggregations.go new file mode 100644 index 000000000..113cb09bb --- /dev/null +++ b/internal/resources/fabric/connection_route_aggregation/datasource_all_connectionRouteAggregations.go @@ -0,0 +1,57 @@ +package connection_route_aggregation + +import ( + "context" + equinix_errors "github.com/equinix/terraform-provider-equinix/internal/errors" + "github.com/equinix/terraform-provider-equinix/internal/framework" + "github.com/hashicorp/terraform-plugin-framework/datasource" +) + +func NewDataSourceAllConnectionRouteAggregations() datasource.DataSource { + return &DataSourceAllConnectionRouteAggregations{ + BaseDataSource: framework.NewBaseDataSource( + framework.BaseDataSourceConfig{ + Name: "equinix_fabric_connection_route_aggregations", + }, + ), + } +} + +type DataSourceAllConnectionRouteAggregations struct { + framework.BaseDataSource +} + +func (r *DataSourceAllConnectionRouteAggregations) Schema( + ctx context.Context, + req datasource.SchemaRequest, + resp *datasource.SchemaResponse, +) { + resp.Schema = dataSourceAllConnectionRouteAggregationSchema(ctx) +} + +func (r *DataSourceAllConnectionRouteAggregations) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) { + client := r.Meta.NewFabricClientForFramework(ctx, request.ProviderMeta) + + var data DatsSourceAllConnectionRouteAggregationModel + response.Diagnostics.Append(request.Config.Get(ctx, &data)...) + if response.Diagnostics.HasError() { + return + } + + connectionId := data.ConnectionId.ValueString() + + connectionRouteAggregations, _, err := client.RouteAggregationsApi.GetConnectionRouteAggregations(ctx, connectionId).Execute() + + if err != nil { + response.State.RemoveResource(ctx) + response.Diagnostics.AddError("api error retrieving connection route aggregations data", equinix_errors.FormatFabricError(err).Error()) + return + } + + response.Diagnostics.Append(data.parse(ctx, connectionRouteAggregations)...) + if response.Diagnostics.HasError() { + return + } + + response.Diagnostics.Append(response.State.Set(ctx, &data)...) +} diff --git a/internal/resources/fabric/connection_route_aggregation/datasource_by_connectionRouteAggregationId.go b/internal/resources/fabric/connection_route_aggregation/datasource_by_connectionRouteAggregationId.go new file mode 100644 index 000000000..e87de3303 --- /dev/null +++ b/internal/resources/fabric/connection_route_aggregation/datasource_by_connectionRouteAggregationId.go @@ -0,0 +1,59 @@ +package connection_route_aggregation + +import ( + "context" + equinix_errors "github.com/equinix/terraform-provider-equinix/internal/errors" + "github.com/equinix/terraform-provider-equinix/internal/framework" + "github.com/hashicorp/terraform-plugin-framework/datasource" +) + +func NewDataSourceByConnectionRouteAggregationID() datasource.DataSource { + return &DataSourceByConnectionRouteAggregationID{ + BaseDataSource: framework.NewBaseDataSource( + framework.BaseDataSourceConfig{ + Name: "equinix_fabric_connection_route_aggregation", + }, + ), + } +} + +type DataSourceByConnectionRouteAggregationID struct { + framework.BaseDataSource +} + +func (r *DataSourceByConnectionRouteAggregationID) Schema( + ctx context.Context, + req datasource.SchemaRequest, + resp *datasource.SchemaResponse, +) { + resp.Schema = dataSourceSingleConnectionRouteAggregationSchema(ctx) +} + +func (r *DataSourceByConnectionRouteAggregationID) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) { + client := r.Meta.NewFabricClientForFramework(ctx, request.ProviderMeta) + + var data DataSourceByIdModel + response.Diagnostics.Append(request.Config.Get(ctx, &data)...) + if response.Diagnostics.HasError() { + return + } + + routeAggregationId := data.RouteAggregationId.ValueString() + connectionId := data.ConnectionId.ValueString() + + routeAggregation, _, err := client.RouteAggregationsApi.GetConnectionRouteAggregationByUuid(ctx, routeAggregationId, connectionId).Execute() + + if err != nil { + response.State.RemoveResource(ctx) + response.Diagnostics.AddError("api error retrieving connection route aggregation data", equinix_errors.FormatFabricError(err).Error()) + return + } + + response.Diagnostics.Append(data.parse(ctx, routeAggregation)...) + if response.Diagnostics.HasError() { + return + } + + response.Diagnostics.Append(response.State.Set(ctx, &data)...) + +} diff --git a/internal/resources/fabric/connection_route_aggregation/datasource_schema.go b/internal/resources/fabric/connection_route_aggregation/datasource_schema.go new file mode 100644 index 000000000..ec6f01191 --- /dev/null +++ b/internal/resources/fabric/connection_route_aggregation/datasource_schema.go @@ -0,0 +1,111 @@ +package connection_route_aggregation + +import ( + "context" + "github.com/equinix/terraform-provider-equinix/internal/framework" + fwtypes "github.com/equinix/terraform-provider-equinix/internal/framework/types" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" +) + +func dataSourceAllConnectionRouteAggregationSchema(ctx context.Context) schema.Schema { + return schema.Schema{ + Description: `Fabric V4 API compatible data resource that allow user to fetch Equinix Fabric Connection Route Aggregations with pagination details +Additional Documentation: +* API: https://developer.equinix.com/catalog/fabricv4#tag/Route-Aggregations`, + Attributes: map[string]schema.Attribute{ + "id": framework.IDAttributeDefaultDescription(), + "connection_id": schema.StringAttribute{ + Description: "The uuid of the connection this data source should retrieve", + Required: true, + }, + "data": schema.ListNestedAttribute{ + Description: "Returned list of connection route aggregation objects", + Computed: true, + CustomType: fwtypes.NewListNestedObjectTypeOf[BaseConnectionRouteAggregationModel](ctx), + NestedObject: schema.NestedAttributeObject{ + Attributes: getConnectionRouteAggregationSchema(ctx), + }, + }, + "pagination": schema.SingleNestedAttribute{ + Description: "Pagination details for the returned connection route aggregations list", + Optional: true, + CustomType: fwtypes.NewObjectTypeOf[PaginationModel](ctx), + Attributes: map[string]schema.Attribute{ + "offset": schema.Int32Attribute{ + Description: "Index of the first item returned in the response. The default is 0", + Optional: true, + Computed: true, + }, + "limit": schema.Int32Attribute{ + Description: "Maximum number of search results returned per page. Number must be between 1 and 100, and the default is 20", + Optional: true, + Computed: true, + }, + "total": schema.Int32Attribute{ + Description: "The total number of connection route aggregations available to the user making the request", + Optional: true, + Computed: true, + }, + "next": schema.StringAttribute{ + Description: "The URL relative to the next item in the response", + Optional: true, + Computed: true, + }, + "previous": schema.StringAttribute{ + Description: "The URL relative to the previous item in the response", + Optional: true, + Computed: true, + }, + }, + }, + }, + } +} + +func dataSourceSingleConnectionRouteAggregationSchema(ctx context.Context) schema.Schema { + baseConnectionRouteAggregationSchema := getConnectionRouteAggregationSchema(ctx) + baseConnectionRouteAggregationSchema["id"] = framework.IDAttributeDefaultDescription() + baseConnectionRouteAggregationSchema["route_aggregation_id"] = schema.StringAttribute{ + Description: "The uuid of the route aggregation this data source should retrieve", + Required: true, + } + baseConnectionRouteAggregationSchema["connection_id"] = schema.StringAttribute{ + Description: "The uuid of the connection this data source should retrieve", + Required: true, + } + return schema.Schema{ + Description: `Fabric V4 API compatible data resource that allow user to fetch Equinix Fabric Connection Route Aggregation by UUID +Additional Documentation: +* API: https://developer.equinix.com/catalog/fabricv4#tag/Route-Aggregations`, + Attributes: baseConnectionRouteAggregationSchema, + } +} + +func getConnectionRouteAggregationSchema(ctx context.Context) map[string]schema.Attribute { + return map[string]schema.Attribute{ + "route_aggregation_id": schema.StringAttribute{ + Description: "UUID of the Route Aggregation to attach this Connection to", + Required: true, + }, + "connection_id": schema.StringAttribute{ + Description: "UUID of the Connection to attach this Route Aggregation to", + Required: true, + }, + "href": schema.StringAttribute{ + Description: "URI to the attached Route Aggregation Policy on the Connection", + Computed: true, + }, + "type": schema.StringAttribute{ + Description: "Route Aggregation Type. One of [\"BGP_IPv4_PREFIX_AGGREGATION\"]", + Computed: true, + }, + "uuid": schema.StringAttribute{ + Description: "Equinix Assigned ID for Route Aggregation Policy", + Computed: true, + }, + "attachment_status": schema.StringAttribute{ + Description: "Status of the Route Aggregation Policy attachment lifecycle", + Computed: true, + }, + } +} diff --git a/internal/resources/fabric/connection_route_aggregation/datasource_test.go b/internal/resources/fabric/connection_route_aggregation/datasource_test.go new file mode 100644 index 000000000..20fafa39c --- /dev/null +++ b/internal/resources/fabric/connection_route_aggregation/datasource_test.go @@ -0,0 +1,59 @@ +package connection_route_aggregation_test + +import ( + "fmt" + "github.com/equinix/terraform-provider-equinix/internal/acceptance" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "testing" +) + +func testAccFabricConnectionRouteAggregationDataSourcesConfig() string { + return fmt.Sprintf(` + resource "equinix_fabric_connection_route_aggregation" "test" { + route_aggregation_id = "f264c892-bfd4-4823-ab8f-7ee74cc7a0f0" + connection_id = "daa424a5-e7b1-4fb6-bdbc-200e9b4757d1" + } + + data "equinix_fabric_connection_route_aggregation" "data_cra" { + depends_on = [equinix_fabric_connection_route_aggregation.test] + route_aggregation_id = "f264c892-bfd4-4823-ab8f-7ee74cc7a0f0" + connection_id = "daa424a5-e7b1-4fb6-bdbc-200e9b4757d1" + } + + + data "equinix_fabric_connection_route_aggregations" "data_cras" { + depends_on = [equinix_fabric_connection_route_aggregation.test] + connection_id = "daa424a5-e7b1-4fb6-bdbc-200e9b4757d1" + }`) +} + +func TestAccFabricConnectionRouteAggregationDataSources_PFCR(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.TestAccPreCheck(t); acceptance.TestAccPreCheckProviderConfigured(t) }, + ExternalProviders: acceptance.TestExternalProviders, + ProtoV6ProviderFactories: acceptance.ProtoV6ProviderFactories, + CheckDestroy: CheckConnectionRouteAggregationDelete, + Steps: []resource.TestStep{ + { + Config: testAccFabricConnectionRouteAggregationDataSourcesConfig(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "data.equinix_fabric_connection_route_aggregation.data_cra", "attachment_status", "ATTACHED"), + resource.TestCheckResourceAttr( + "data.equinix_fabric_connection_route_aggregation.data_cra", "type", "BGP_IPv4_PREFIX_AGGREGATION"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_connection_route_aggregation.data_cra", "href"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_connection_route_aggregation.data_cra", "uuid"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_connection_route_aggregations.data_cras", "data.0.attachment_status"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_connection_route_aggregations.data_cras", "data.0.type"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_connection_route_aggregations.data_cras", "data.0.href"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_connection_route_aggregations.data_cras", "data.0.uuid"), + resource.TestCheckResourceAttr("data.equinix_fabric_connection_route_aggregations.data_cras", "data.#", "1"), + resource.TestCheckResourceAttr("data.equinix_fabric_connection_route_aggregations.data_cras", "pagination.%", "5"), + resource.TestCheckResourceAttr("data.equinix_fabric_connection_route_aggregations.data_cras", "pagination.limit", "10"), + resource.TestCheckResourceAttr("data.equinix_fabric_connection_route_aggregations.data_cras", "pagination.offset", "0"), + ), + ExpectNonEmptyPlan: false, + }, + }, + }) +} diff --git a/internal/resources/fabric/connection_route_aggregation/models.go b/internal/resources/fabric/connection_route_aggregation/models.go new file mode 100644 index 000000000..4e02dc152 --- /dev/null +++ b/internal/resources/fabric/connection_route_aggregation/models.go @@ -0,0 +1,138 @@ +package connection_route_aggregation + +import ( + "context" + "github.com/equinix/equinix-sdk-go/services/fabricv4" + fwtypes "github.com/equinix/terraform-provider-equinix/internal/framework/types" + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +type DataSourceByIdModel struct { + ID types.String `tfsdk:"id"` + BaseConnectionRouteAggregationModel +} + +type DatsSourceAllConnectionRouteAggregationModel struct { + ID types.String `tfsdk:"id"` + ConnectionId types.String `tfsdk:"connection_id"` + Data fwtypes.ListNestedObjectValueOf[BaseConnectionRouteAggregationModel] `tfsdk:"data"` + Pagination fwtypes.ObjectValueOf[PaginationModel] `tfsdk:"pagination"` +} + +type PaginationModel struct { + Offset types.Int32 `tfsdk:"offset"` + Limit types.Int32 `tfsdk:"limit"` + Total types.Int32 `tfsdk:"total"` + Next types.String `tfsdk:"next"` + Previous types.String `tfsdk:"previous"` +} + +type ResourceModel struct { + ID types.String `tfsdk:"id"` + Timeouts timeouts.Value `tfsdk:"timeouts"` + BaseConnectionRouteAggregationModel +} + +type BaseConnectionRouteAggregationModel struct { + RouteAggregationId types.String `tfsdk:"route_aggregation_id"` + ConnectionId types.String `tfsdk:"connection_id"` + Href types.String `tfsdk:"href"` + Type types.String `tfsdk:"type"` + Uuid types.String `tfsdk:"uuid"` + AttachmentStatus types.String `tfsdk:"attachment_status"` +} + +func (m *DataSourceByIdModel) parse(ctx context.Context, connectionRouteAggregation *fabricv4.ConnectionRouteAggregationData) diag.Diagnostics { + m.ID = types.StringValue(connectionRouteAggregation.GetUuid()) + + diags := parseConnectionRouteAggregation(ctx, connectionRouteAggregation, + &m.Href, + &m.Type, + &m.Uuid, + &m.AttachmentStatus) + if diags.HasError() { + return diags + } + return diags +} + +func (m *DatsSourceAllConnectionRouteAggregationModel) parse(ctx context.Context, connectionRouteAggregationsResponse *fabricv4.GetAllConnectionRouteAggregationsResponse) diag.Diagnostics { + var diags diag.Diagnostics + + if len(connectionRouteAggregationsResponse.GetData()) < 1 { + diags.AddError("no data retrieved by connection route aggrgeations data source", "either the account does not have any connection route aggregation data to pull or the combination of limit and offset needs to be updated") + return diags + } + + data := make([]BaseConnectionRouteAggregationModel, len(connectionRouteAggregationsResponse.GetData())) + connectionRouteAggregations := connectionRouteAggregationsResponse.GetData() + for index, routeAggregationRule := range connectionRouteAggregations { + var connectionRouteAggregationModel BaseConnectionRouteAggregationModel + diags = connectionRouteAggregationModel.parse(ctx, &routeAggregationRule) + if diags.HasError() { + return diags + } + data[index] = connectionRouteAggregationModel + } + responsePagination := connectionRouteAggregationsResponse.GetPagination() + pagination := PaginationModel{ + Offset: types.Int32Value(responsePagination.GetOffset()), + Limit: types.Int32Value(responsePagination.GetLimit()), + Total: types.Int32Value(responsePagination.GetTotal()), + Next: types.StringValue(responsePagination.GetNext()), + Previous: types.StringValue(responsePagination.GetPrevious()), + } + m.ID = types.StringValue(data[0].Uuid.ValueString()) + m.Pagination = fwtypes.NewObjectValueOf[PaginationModel](ctx, &pagination) + + dataPtr := make([]*BaseConnectionRouteAggregationModel, len(data)) + for i := range data { + dataPtr[i] = &data[i] + } + m.Data = fwtypes.NewListNestedObjectValueOfSlice[BaseConnectionRouteAggregationModel](ctx, dataPtr) + + return diags +} + +func (m *ResourceModel) parse(ctx context.Context, connectionRouteAggregation *fabricv4.ConnectionRouteAggregationData) diag.Diagnostics { + var diags diag.Diagnostics + + m.ID = types.StringValue(connectionRouteAggregation.GetUuid()) + + diags = parseConnectionRouteAggregation(ctx, connectionRouteAggregation, + &m.Href, + &m.Type, + &m.Uuid, + &m.AttachmentStatus) + if diags.HasError() { + return diags + } + return diags +} + +func (m *BaseConnectionRouteAggregationModel) parse(ctx context.Context, connectionRouteAggregation *fabricv4.ConnectionRouteAggregationData) diag.Diagnostics { + var diags diag.Diagnostics = parseConnectionRouteAggregation(ctx, connectionRouteAggregation, + &m.Href, + &m.Type, + &m.Uuid, + &m.AttachmentStatus) + if diags.HasError() { + return diags + } + return diags +} + +func parseConnectionRouteAggregation(ctx context.Context, connectionRouteAggregation *fabricv4.ConnectionRouteAggregationData, + href, type_, uuid, attachmentStatus *basetypes.StringValue) diag.Diagnostics { + var diag diag.Diagnostics + + *href = types.StringValue(connectionRouteAggregation.GetHref()) + *type_ = types.StringValue(string(connectionRouteAggregation.GetType())) + *uuid = types.StringValue(connectionRouteAggregation.GetUuid()) + *attachmentStatus = types.StringValue(string(connectionRouteAggregation.GetAttachmentStatus())) + + return diag +} diff --git a/internal/resources/fabric/connection_route_aggregation/resource.go b/internal/resources/fabric/connection_route_aggregation/resource.go new file mode 100644 index 000000000..3b04de296 --- /dev/null +++ b/internal/resources/fabric/connection_route_aggregation/resource.go @@ -0,0 +1,212 @@ +package connection_route_aggregation + +import ( + "context" + "fmt" + "github.com/equinix/equinix-sdk-go/services/fabricv4" + equinix_errors "github.com/equinix/terraform-provider-equinix/internal/errors" + "github.com/equinix/terraform-provider-equinix/internal/framework" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "net/http" + "slices" + "time" +) + +func NewResource() resource.Resource { + return &Resource{ + BaseResource: framework.NewBaseResource( + framework.BaseResourceConfig{ + Name: "equinix_fabric_connection_route_aggregation", + }, + ), + } +} + +type Resource struct { + framework.BaseResource +} + +func (r Resource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { + //TODO implement me + panic("implement me") +} + +func (r Resource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = resourceSchema(ctx) +} + +func (r *Resource) Create( + ctx context.Context, + req resource.CreateRequest, + resp *resource.CreateResponse, +) { + var plan ResourceModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + client := r.Meta.NewFabricClientForFramework(ctx, req.ProviderMeta) + + routeAggregationId := plan.RouteAggregationId.ValueString() + connectionId := plan.ConnectionId.ValueString() + + connectionRouteAggregation, _, err := client.RouteAggregationsApi.AttachConnectionRouteAggregation(ctx, routeAggregationId, connectionId).Execute() + + if err != nil { + resp.Diagnostics.AddError("Failed attaching connection to route aggregation", equinix_errors.FormatFabricError(err).Error()) + return + } + + createTimeout, diags := plan.Timeouts.Create(ctx, 10*time.Minute) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + createWaiter := getCreateUpdateWaiter(ctx, client, routeAggregationId, connectionId, createTimeout) + connectionRouteAggregationChecked, err := createWaiter.WaitForStateContext(ctx) + if err != nil { + resp.Diagnostics.AddError(fmt.Sprintf("Failed attaching Route Aggregation %s", connectionRouteAggregation.GetUuid()), err.Error()) + return + } + + resp.Diagnostics.Append(diags...) + if diags.HasError() { + return + } + + resp.Diagnostics.Append(plan.parse(ctx, connectionRouteAggregationChecked.(*fabricv4.ConnectionRouteAggregationData))...) + if resp.Diagnostics.HasError() { + return + } + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + +} + +func (r *Resource) Read( + ctx context.Context, + req resource.ReadRequest, + resp *resource.ReadResponse, +) { + var state ResourceModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + //Retrieve the API client from the provider metadata + client := r.Meta.NewFabricClientForFramework(ctx, req.ProviderMeta) + + id := state.ID.ValueString() + routeAggregationId := state.RouteAggregationId.ValueString() + connectionId := state.ConnectionId.ValueString() + + connectionRouteAggregation, _, err := client.RouteAggregationsApi.GetConnectionRouteAggregationByUuid(ctx, routeAggregationId, connectionId).Execute() + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Failed retrieving Connection Route Aggregation Attachment %s", id), equinix_errors.FormatFabricError(err).Error()) + return + } + + // Set state to fully populated data + resp.Diagnostics.Append(state.parse(ctx, connectionRouteAggregation)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *Resource) Delete( + ctx context.Context, + req resource.DeleteRequest, + resp *resource.DeleteResponse, +) { + //Retrieve the API client + client := r.Meta.NewFabricClientForFramework(ctx, req.ProviderMeta) + + //Retrieve the current state + var state ResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + id := state.ID.ValueString() + routeAggregationId := state.RouteAggregationId.ValueString() + connectionId := state.ConnectionId.ValueString() + + _, deleteResp, err := client.RouteAggregationsApi.DetachConnectionRouteAggregation(ctx, routeAggregationId, connectionId).Execute() + + if err != nil { + if deleteResp == nil || !slices.Contains([]int{http.StatusForbidden, http.StatusNotFound}, deleteResp.StatusCode) { + resp.Diagnostics.AddError(fmt.Sprintf("Failed detaching Connection Route Aggregation %s", id), equinix_errors.FormatFabricError(err).Error()) + return + } + } + + deleteTimeout, diags := state.Timeouts.Delete(ctx, 10*time.Minute) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + deletewaiter := getDeleteWaiter(ctx, client, routeAggregationId, connectionId, deleteTimeout) + _, err = deletewaiter.WaitForStateContext(ctx) + + if err != nil { + resp.Diagnostics.AddError(fmt.Sprintf("Failed detaching Connection Route Aggregation %s", id), err.Error()) + return + } +} + +func getCreateUpdateWaiter(ctx context.Context, client *fabricv4.APIClient, routeAggregationId string, connectionId string, timeout time.Duration) *retry.StateChangeConf { + return &retry.StateChangeConf{ + Pending: []string{ + string(fabricv4.CONNECTIONROUTEAGGREGATIONDATAATTACHMENTSTATUS_ATTACHING), + }, + Target: []string{ + string(fabricv4.CONNECTIONROUTEAGGREGATIONDATAATTACHMENTSTATUS_ATTACHED), + }, + Refresh: func() (interface{}, string, error) { + connectionRouteAggregation, _, err := client.RouteAggregationsApi.GetConnectionRouteAggregationByUuid(ctx, routeAggregationId, connectionId).Execute() + if err != nil { + return 0, "", err + } + return connectionRouteAggregation, string(connectionRouteAggregation.GetAttachmentStatus()), nil + }, + Timeout: timeout, + Delay: 30 * time.Second, + MinTimeout: 30 * time.Second, + } +} + +func getDeleteWaiter(ctx context.Context, client *fabricv4.APIClient, routeAggregationId string, connectionId string, timeout time.Duration) *retry.StateChangeConf { + // deletedMarker is a terraform-provider-only value that is used by the waiter + // to indicate that the resource appears to be deleted successfully based on + // status code or specific error code + deletedMarker := "tf-marker-for-deleted-route-aggregation-rule" + return &retry.StateChangeConf{ + Pending: []string{ + string(fabricv4.CONNECTIONROUTEAGGREGATIONDATAATTACHMENTSTATUS_DETACHING), + }, + Target: []string{ + string(fabricv4.CONNECTIONROUTEAGGREGATIONDATAATTACHMENTSTATUS_DETACHED), + deletedMarker, + }, + Refresh: func() (interface{}, string, error) { + routeAggregationRule, resp, err := client.RouteAggregationsApi.GetConnectionRouteAggregationByUuid(ctx, routeAggregationId, connectionId).Execute() + if err != nil { + if resp != nil && slices.Contains([]int{http.StatusForbidden, http.StatusNotFound}, resp.StatusCode) { + return routeAggregationRule, deletedMarker, nil + } + return 0, "", err + } + return routeAggregationRule, string(routeAggregationRule.GetAttachmentStatus()), nil + }, + Timeout: timeout, + Delay: 10 * time.Second, + MinTimeout: 5 * time.Second, + } +} diff --git a/internal/resources/fabric/connection_route_aggregation/resource_schema.go b/internal/resources/fabric/connection_route_aggregation/resource_schema.go new file mode 100644 index 000000000..abd79275c --- /dev/null +++ b/internal/resources/fabric/connection_route_aggregation/resource_schema.go @@ -0,0 +1,46 @@ +package connection_route_aggregation + +import ( + "context" + "github.com/equinix/terraform-provider-equinix/internal/framework" + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" +) + +func resourceSchema(ctx context.Context) schema.Schema { + return schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": framework.IDAttributeDefaultDescription(), + "timeouts": timeouts.Attributes(ctx, timeouts.Opts{ + Create: true, + Read: true, + Update: true, + Delete: true, + }), + "route_aggregation_id": schema.StringAttribute{ + Description: "UUID of the Route Aggregation to apply this Rule to", + Required: true, + }, + "connection_id": schema.StringAttribute{ + Description: "Equinix Assigned UUID of the Equinix Connection to attach the Route Aggregation Policy to", + Required: true, + }, + "href": schema.StringAttribute{ + Description: "URI to the attached Route Aggregation Policy on the Connection", + Computed: true, + }, + "type": schema.StringAttribute{ + Description: "Route Aggregation Type. One of [\"BGP_IPv4_PREFIX_AGGREGATION\"]", + Computed: true, + }, + "uuid": schema.StringAttribute{ + Description: "Equinix Assigned ID for Route Aggregation Policy", + Computed: true, + }, + "attachment_status": schema.StringAttribute{ + Description: "Status of the Route Aggregation Policy attachment lifecycle", + Computed: true, + }, + }, + } +} diff --git a/internal/resources/fabric/connection_route_aggregation/resource_test.go b/internal/resources/fabric/connection_route_aggregation/resource_test.go new file mode 100644 index 000000000..ced82c310 --- /dev/null +++ b/internal/resources/fabric/connection_route_aggregation/resource_test.go @@ -0,0 +1,143 @@ +package connection_route_aggregation_test + +import ( + "context" + "fmt" + "github.com/equinix/equinix-sdk-go/services/fabricv4" + "github.com/equinix/terraform-provider-equinix/internal/acceptance" + "github.com/equinix/terraform-provider-equinix/internal/config" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "testing" +) + +func testAccFabricConnectionRouteAggregationConfig(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 = "4f855852-eb47-4721-8e40-b386a3676abf" + } + account { + account_number = 77733367 + } + } + + 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 = "4f855852-eb47-4721-8e40-b386a3676abf" + } + z_side { + access_point { + type = "COLO" + port{ + uuid = "%s" + } + link_protocol { + type= "DOT1Q" + vlan_tag= 2571 + } + location { + metro_code = "DC" + } + } + } + } + + //resource "equinix_fabric_route_aggregation" "test" { + // type = "BGP_IPv4_PREFIX_AGGREGATION" + // name = "Route_Aggregation_Test" + // description = "Test Route Aggregation" + // project = { + // project_id = "4f855852-eb47-4721-8e40-b386a3676abf" + // } + //} + + resource "equinix_fabric_connection_route_aggregation" "test" { + route_aggregation_id = "8f8a2ddb-25f8-416e-ad0a-202a9d2af9e1" + connection_id = equinix_fabric_connection.test.id + } + `, portUuid) +} + +func TestAccFabricConnectionRouteAggregation_PFCR(t *testing.T) { + portId := "c5720fcc-4ae6-ae6e-13e0-306a5c00adaf" + //upRouteAggregationName := "stream_up_PFCR" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.TestAccPreCheck(t); acceptance.TestAccPreCheckProviderConfigured(t) }, + ExternalProviders: acceptance.TestExternalProviders, + ProtoV6ProviderFactories: acceptance.ProtoV6ProviderFactories, + CheckDestroy: CheckConnectionRouteAggregationDelete, + Steps: []resource.TestStep{ + { + Config: testAccFabricConnectionRouteAggregationConfig(portId), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("equinix_fabric_connection_route_aggregation.test", "uuid"), + resource.TestCheckResourceAttrSet("equinix_fabric_connection_route_aggregation.test", "attachment_status"), + resource.TestCheckResourceAttrSet("equinix_fabric_connection_route_aggregation.test", "href"), + resource.TestCheckResourceAttr("equinix_fabric_connection_route_aggregation.test", "type", "BGP_IPv4_PREFIX_AGGREGATION"), + ), + ExpectNonEmptyPlan: false, + }, + }, + }) +} + +func CheckConnectionRouteAggregationDelete(s *terraform.State) error { + ctx := context.Background() + client := acceptance.TestAccProvider.Meta().(*config.Config).NewFabricClientForTesting(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "equinix_fabric_connection_route_aggregation" { + continue + } + + routeAggregationId := rs.Primary.Attributes["route_aggregation_id"] + connectionId := rs.Primary.Attributes["connection_id"] + + if connectionRouteAggregation, _, err := client.RouteAggregationsApi.GetConnectionRouteAggregationByUuid(ctx, routeAggregationId, connectionId).Execute(); err == nil && + connectionRouteAggregation.GetAttachmentStatus() == fabricv4.CONNECTIONROUTEAGGREGATIONDATAATTACHMENTSTATUS_ATTACHED { + return fmt.Errorf("fabric connection route aggregation attchement %s still exists and is %s", + rs.Primary.ID, string(fabricv4.CONNECTIONROUTEAGGREGATIONDATAATTACHMENTSTATUS_ATTACHED)) + } + } + return nil +} diff --git a/internal/resources/fabric/route_aggregation_rule/datasource_all_routeAggregationRules.go b/internal/resources/fabric/route_aggregation_rule/datasource_all_routeAggregationRules.go new file mode 100644 index 000000000..1910122de --- /dev/null +++ b/internal/resources/fabric/route_aggregation_rule/datasource_all_routeAggregationRules.go @@ -0,0 +1,69 @@ +package route_aggregation_rule + +import ( + "context" + equinix_errors "github.com/equinix/terraform-provider-equinix/internal/errors" + "github.com/equinix/terraform-provider-equinix/internal/framework" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +func NewDataSourceAllRouteAggregationRule() datasource.DataSource { + return &DataSourceAllRouteAggregationRules{ + BaseDataSource: framework.NewBaseDataSource( + framework.BaseDataSourceConfig{ + Name: "equinix_fabric_route_aggregation_rules", + }, + ), + } +} + +type DataSourceAllRouteAggregationRules struct { + framework.BaseDataSource +} + +func (r *DataSourceAllRouteAggregationRules) Schema( + ctx context.Context, + req datasource.SchemaRequest, + resp *datasource.SchemaResponse, +) { + resp.Schema = dataSourceAllRouteAggregationRulesSchema(ctx) +} + +func (r *DataSourceAllRouteAggregationRules) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) { + client := r.Meta.NewFabricClientForFramework(ctx, request.ProviderMeta) + + var data DatsSourceAllRouteAggregationRulesModel + response.Diagnostics.Append(request.Config.Get(ctx, &data)...) + if response.Diagnostics.HasError() { + return + } + + routeAggregationId := data.RouteAggregationId.ValueString() + + var tfpagination PaginationModel + diags := data.Pagination.As(ctx, &tfpagination, basetypes.ObjectAsOptions{}) + if diags.HasError() { + return + } + offset := tfpagination.Offset.ValueInt32() + limit := tfpagination.Limit.ValueInt32() + if limit == 0 { + limit = 20 + } + + routeAggregations, _, err := client.RouteAggregationRulesApi.GetRouteAggregationRules(ctx, routeAggregationId).Limit(limit).Offset(offset).Execute() + + if err != nil { + response.State.RemoveResource(ctx) + response.Diagnostics.AddError("api error retrieving route aggregation rules data", equinix_errors.FormatFabricError(err).Error()) + return + } + + response.Diagnostics.Append(data.parse(ctx, routeAggregations)...) + if response.Diagnostics.HasError() { + return + } + + response.Diagnostics.Append(response.State.Set(ctx, &data)...) +} diff --git a/internal/resources/fabric/route_aggregation_rule/datasource_by_routeAggregationRuleId.go b/internal/resources/fabric/route_aggregation_rule/datasource_by_routeAggregationRuleId.go new file mode 100644 index 000000000..c423ebc18 --- /dev/null +++ b/internal/resources/fabric/route_aggregation_rule/datasource_by_routeAggregationRuleId.go @@ -0,0 +1,60 @@ +package route_aggregation_rule + +import ( + "context" + + equinix_errors "github.com/equinix/terraform-provider-equinix/internal/errors" + "github.com/equinix/terraform-provider-equinix/internal/framework" + "github.com/hashicorp/terraform-plugin-framework/datasource" +) + +func NewDataSourceByRouteAggregationRuleID() datasource.DataSource { + return &DataSourceByRouteAggregationRuleID{ + BaseDataSource: framework.NewBaseDataSource( + framework.BaseDataSourceConfig{ + Name: "equinix_fabric_route_aggregation_rule", + }, + ), + } +} + +type DataSourceByRouteAggregationRuleID struct { + framework.BaseDataSource +} + +func (r *DataSourceByRouteAggregationRuleID) Schema( + ctx context.Context, + req datasource.SchemaRequest, + resp *datasource.SchemaResponse, +) { + resp.Schema = dataSourceSingleRouteAggregationRuleSchema(ctx) +} + +func (r *DataSourceByRouteAggregationRuleID) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) { + client := r.Meta.NewFabricClientForFramework(ctx, request.ProviderMeta) + + var data DataSourceByIdModel + response.Diagnostics.Append(request.Config.Get(ctx, &data)...) + if response.Diagnostics.HasError() { + return + } + + routeAggregationRuleID := data.RouteAggregationRuleId.ValueString() + routeAggregationId := data.RouteAggregationID.ValueString() + + routeAggregation, _, err := client.RouteAggregationRulesApi.GetRouteAggregationRuleByUuid(ctx, routeAggregationId, routeAggregationRuleID).Execute() + + if err != nil { + response.State.RemoveResource(ctx) + response.Diagnostics.AddError("api error retrieving route aggregation rule data", equinix_errors.FormatFabricError(err).Error()) + return + } + + response.Diagnostics.Append(data.parse(ctx, routeAggregation)...) + if response.Diagnostics.HasError() { + return + } + + response.Diagnostics.Append(response.State.Set(ctx, &data)...) + +} diff --git a/internal/resources/fabric/route_aggregation_rule/datasource_schema.go b/internal/resources/fabric/route_aggregation_rule/datasource_schema.go new file mode 100644 index 000000000..e441709a1 --- /dev/null +++ b/internal/resources/fabric/route_aggregation_rule/datasource_schema.go @@ -0,0 +1,191 @@ +package route_aggregation_rule + +import ( + "context" + "github.com/equinix/terraform-provider-equinix/internal/framework" + fwtypes "github.com/equinix/terraform-provider-equinix/internal/framework/types" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" +) + +func dataSourceAllRouteAggregationRulesSchema(ctx context.Context) schema.Schema { + return schema.Schema{ + Description: `Fabric V4 API compatible data resource that allow user to fetch Equinix Fabric Route Aggregation Rules with pagination details +Additional Documentation: +* API: https://developer.equinix.com/catalog/fabricv4#tag/Route-Aggregations`, + Attributes: map[string]schema.Attribute{ + "id": framework.IDAttributeDefaultDescription(), + "route_aggregation_id": schema.StringAttribute{ + Description: "The uuid of the route aggregation rule this data source should retrieve", + Required: true, + }, + "data": schema.ListNestedAttribute{ + Description: "Returned list of route aggregation rule objects", + Computed: true, + CustomType: fwtypes.NewListNestedObjectTypeOf[BaseRouteAggregationRuleModel](ctx), + NestedObject: schema.NestedAttributeObject{ + Attributes: getRouteAggregationRuleSchema(ctx), + }, + }, + "pagination": schema.SingleNestedAttribute{ + Description: "Pagination details for the returned route aggregation rules list", + Optional: true, + CustomType: fwtypes.NewObjectTypeOf[PaginationModel](ctx), + Attributes: map[string]schema.Attribute{ + "offset": schema.Int32Attribute{ + Description: "Index of the first item returned in the response. The default is 0", + Optional: true, + Computed: true, + }, + "limit": schema.Int32Attribute{ + Description: "Maximum number of search results returned per page. Number must be between 1 and 100, and the default is 20", + Optional: true, + Computed: true, + }, + "total": schema.Int32Attribute{ + Description: "The total number of route agrgegation rules available to the user making the request", + Computed: true, + }, + "next": schema.StringAttribute{ + Description: "The URL relative to the next item in the response", + Computed: true, + }, + "previous": schema.StringAttribute{ + Description: "The URL relative to the previous item in the response", + Computed: true, + }, + }, + }, + }, + } +} + +func dataSourceSingleRouteAggregationRuleSchema(ctx context.Context) schema.Schema { + baseRouteAggregationRuleSchema := getRouteAggregationRuleSchema(ctx) + baseRouteAggregationRuleSchema["id"] = framework.IDAttributeDefaultDescription() + baseRouteAggregationRuleSchema["route_aggregation_rule_id"] = schema.StringAttribute{ + Description: "The uuid of the route aggregation rule this data source should retrieve", + Required: true, + } + baseRouteAggregationRuleSchema["route_aggregation_id"] = schema.StringAttribute{ + Description: "The uuid of the route aggregation this data source should retrieve", + Required: true, + } + return schema.Schema{ + Description: `Fabric V4 API compatible data resource that allow user to fetch Equinix Fabric Route Aggregation Rule by UUID +Additional Documentation: +* API: https://developer.equinix.com/catalog/fabricv4#tag/Route-Aggregations`, + Attributes: baseRouteAggregationRuleSchema, + } + +} + +func getRouteAggregationRuleSchema(ctx context.Context) map[string]schema.Attribute { + return map[string]schema.Attribute{ + "route_aggregation_id": schema.StringAttribute{ + Description: "UUID of the Route Aggregation to apply this Rule to", + Computed: true, + }, + "href": schema.StringAttribute{ + Description: "Equinix auto generated URI to the route aggregation rule resource", + Computed: true, + }, + "type": schema.StringAttribute{ + Description: "Equinix defined Route Aggregation Type; BGP_IPv4_PREFIX_AGGREGATION, BGP_IPv6_PREFIX_AGGREGATION", + Computed: true, + }, + "uuid": schema.StringAttribute{ + Description: "Equinix-assigned unique id for the route aggregation rule resource", + Computed: true, + }, + "name": schema.StringAttribute{ + Description: "Customer provided name of the route aggregation rule", + Computed: true, + }, + "description": schema.StringAttribute{ + Description: "Customer-provided route aggregation rule description", + Optional: true, + }, + "state": schema.StringAttribute{ + Description: "Value representing provisioning status for the route aggregation rule resource", + Computed: true, + }, + "prefix": schema.StringAttribute{ + Description: "Customer-provided route aggregation rule prefix", + Computed: true, + }, + "change": schema.SingleNestedAttribute{ + Description: "Current state of latest route aggregation rule change", + Computed: true, + CustomType: fwtypes.NewObjectTypeOf[ChangeModel](ctx), + Attributes: map[string]schema.Attribute{ + "uuid": schema.StringAttribute{ + Description: "Equinix-assigned unique id for a change", + Required: true, + }, + "type": schema.StringAttribute{ + Description: "Equinix defined Route Aggregation Change Type", + Required: true, + }, + "href": schema.StringAttribute{ + Description: "Equinix auto generated URI to the route aggregation change", + Computed: true, + }, + }, + }, + "change_log": schema.SingleNestedAttribute{ + Description: "Details of the last change on the stream resource", + Computed: true, + CustomType: fwtypes.NewObjectTypeOf[ChangeLogModel](ctx), + Attributes: map[string]schema.Attribute{ + "created_by": schema.StringAttribute{ + Description: "User name of creator of the stream resource", + Computed: true, + }, + "created_by_full_name": schema.StringAttribute{ + Description: "Legal name of creator of the stream resource", + Computed: true, + }, + "created_by_email": schema.StringAttribute{ + Description: "Email of creator of the stream resource", + Computed: true, + }, + "created_date_time": schema.StringAttribute{ + Description: "Creation time of the stream resource", + Computed: true, + }, + "updated_by": schema.StringAttribute{ + Description: "User name of last updater of the stream resource", + Computed: true, + }, + "updated_by_full_name": schema.StringAttribute{ + Description: "Legal name of last updater of the stream resource", + Computed: true, + }, + "updated_by_email": schema.StringAttribute{ + Description: "Email of last updater of the stream resource", + Computed: true, + }, + "updated_date_time": schema.StringAttribute{ + Description: "Last update time of the stream resource", + Computed: true, + }, + "deleted_by": schema.StringAttribute{ + Description: "User name of deleter of the stream resource", + Computed: true, + }, + "deleted_by_full_name": schema.StringAttribute{ + Description: "Legal name of deleter of the stream resource", + Computed: true, + }, + "deleted_by_email": schema.StringAttribute{ + Description: "Email of deleter of the stream resource", + Computed: true, + }, + "deleted_date_time": schema.StringAttribute{ + Description: "Deletion time of the stream resource", + Computed: true, + }, + }, + }, + } +} diff --git a/internal/resources/fabric/route_aggregation_rule/datasource_test.go b/internal/resources/fabric/route_aggregation_rule/datasource_test.go new file mode 100644 index 000000000..b2e5ad7eb --- /dev/null +++ b/internal/resources/fabric/route_aggregation_rule/datasource_test.go @@ -0,0 +1,78 @@ +package route_aggregation_rule_test + +import ( + "fmt" + "testing" + + "github.com/equinix/terraform-provider-equinix/internal/acceptance" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func testAccFabricRouteAggregationRuleDataSourcesConfig(name, description string) string { + return fmt.Sprintf(` + + + resource "equinix_fabric_route_aggregation_rule" "new-rar" { + route_aggregation_id = "8f8a2ddb-25f8-416e-ad0a-202a9d2af9e1" + name = "%[1]s" + description = "%[2]s" + prefix = "192.166.0.0/24" + } + + data "equinix_fabric_route_aggregation_rule" "data_rar" { + route_aggregation_id = "8f8a2ddb-25f8-416e-ad0a-202a9d2af9e1" + route_aggregation_rule_id = equinix_fabric_route_aggregation_rule.new-rar.id + } + + + data "equinix_fabric_route_aggregation_rules" "data_rars" { + depends_on = [equinix_fabric_route_aggregation_rule.new-rar,] + route_aggregation_id = "8f8a2ddb-25f8-416e-ad0a-202a9d2af9e1" + pagination = { + limit = 2 + offset = 1 + } + } + `, name, description) +} + +func TestAccFabricRouteAggregationRuleDataSources_PFCR(t *testing.T) { + routeAggregationName := "route_agg_rule_PFCR" + routeAggregatioDescription := "route aggregation rule PFCR" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.TestAccPreCheck(t); acceptance.TestAccPreCheckProviderConfigured(t) }, + ExternalProviders: acceptance.TestExternalProviders, + ProtoV6ProviderFactories: acceptance.ProtoV6ProviderFactories, + CheckDestroy: CheckRouteAggregationRuleDelete, + Steps: []resource.TestStep{ + { + Config: testAccFabricRouteAggregationRuleDataSourcesConfig(routeAggregationName, routeAggregatioDescription), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "data.equinix_fabric_route_aggregation_rule.data_rar", "name", routeAggregationName), + resource.TestCheckResourceAttr( + "data.equinix_fabric_route_aggregation_rule.data_rar", "type", "BGP_IPv4_PREFIX_AGGREGATION_RULE"), + resource.TestCheckResourceAttr( + "data.equinix_fabric_route_aggregation_rule.data_rar", "description", routeAggregatioDescription), + resource.TestCheckResourceAttrSet("data.equinix_fabric_route_aggregation_rule.data_rar", "href"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_route_aggregation_rule.data_rar", "uuid"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_route_aggregation_rule.data_rar", "prefix"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_route_aggregation_rule.data_rar", "prefix"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_route_aggregation_rule.data_rar", "state"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_route_aggregation_rule.data_rar", "change_log.created_by"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_route_aggregation_rules.data_rars", "data.0.name"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_route_aggregation_rules.data_rars", "data.0.type"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_route_aggregation_rules.data_rars", "data.0.description"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_route_aggregation_rules.data_rars", "data.0.href"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_route_aggregation_rules.data_rars", "data.0.uuid"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_route_aggregation_rules.data_rars", "data.0.change_log.created_by"), + resource.TestCheckResourceAttr("data.equinix_fabric_route_aggregation_rules.data_rars", "data.#", "1"), + resource.TestCheckResourceAttr("data.equinix_fabric_route_aggregation_rules.data_rars", "pagination.%", "5"), + resource.TestCheckResourceAttr("data.equinix_fabric_route_aggregation_rules.data_rars", "pagination.limit", "2"), + resource.TestCheckResourceAttr("data.equinix_fabric_route_aggregation_rules.data_rars", "pagination.offset", "1"), + ), + ExpectNonEmptyPlan: false, + }, + }, + }) +} diff --git a/internal/resources/fabric/route_aggregation_rule/models.go b/internal/resources/fabric/route_aggregation_rule/models.go new file mode 100644 index 000000000..6808912ef --- /dev/null +++ b/internal/resources/fabric/route_aggregation_rule/models.go @@ -0,0 +1,210 @@ +package route_aggregation_rule + +import ( + "context" + "github.com/equinix/equinix-sdk-go/services/fabricv4" + fwtypes "github.com/equinix/terraform-provider-equinix/internal/framework/types" + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +type DataSourceByIdModel struct { + RouteAggregationRuleId types.String `tfsdk:"route_aggregation_rule_id"` + ID types.String `tfsdk:"id"` + BaseRouteAggregationRuleModel +} + +type DatsSourceAllRouteAggregationRulesModel struct { + ID types.String `tfsdk:"id"` + Data fwtypes.ListNestedObjectValueOf[BaseRouteAggregationRuleModel] `tfsdk:"data"` + Pagination fwtypes.ObjectValueOf[PaginationModel] `tfsdk:"pagination"` + RouteAggregationId types.String `tfsdk:"route_aggregation_id"` +} + +type PaginationModel struct { + Offset types.Int32 `tfsdk:"offset"` + Limit types.Int32 `tfsdk:"limit"` + Total types.Int32 `tfsdk:"total"` + Next types.String `tfsdk:"next"` + Previous types.String `tfsdk:"previous"` +} + +type ResourceModel struct { + ID types.String `tfsdk:"id"` + Timeouts timeouts.Value `tfsdk:"timeouts"` + BaseRouteAggregationRuleModel +} + +type BaseRouteAggregationRuleModel struct { + RouteAggregationID types.String `tfsdk:"route_aggregation_id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + Prefix types.String `tfsdk:"prefix"` + Href types.String `tfsdk:"href"` + Type types.String `tfsdk:"type"` + Uuid types.String `tfsdk:"uuid"` + State types.String `tfsdk:"state"` + Change fwtypes.ObjectValueOf[ChangeModel] `tfsdk:"change"` + ChangeLog fwtypes.ObjectValueOf[ChangeLogModel] `tfsdk:"change_log"` +} + +type ChangeModel struct { + Uuid types.String `tfsdk:"uuid"` + Type types.String `tfsdk:"type"` + Href types.String `tfsdk:"href"` +} + +type ChangeLogModel struct { + CreatedBy types.String `tfsdk:"created_by"` + CreatedByFullName types.String `tfsdk:"created_by_full_name"` + CreatedByEmail types.String `tfsdk:"created_by_email"` + CreatedDateTime types.String `tfsdk:"created_date_time"` + UpdatedBy types.String `tfsdk:"updated_by"` + UpdatedByFullName types.String `tfsdk:"updated_by_full_name"` + UpdatedByEmail types.String `tfsdk:"updated_by_email"` + UpdatedDateTime types.String `tfsdk:"updated_date_time"` + DeletedBy types.String `tfsdk:"deleted_by"` + DeletedByFullName types.String `tfsdk:"deleted_by_full_name"` + DeletedByEmail types.String `tfsdk:"deleted_by_email"` + DeletedDateTime types.String `tfsdk:"deleted_date_time"` +} + +func (m *DataSourceByIdModel) parse(ctx context.Context, routeAggregationRule *fabricv4.RouteAggregationRulesData) diag.Diagnostics { + m.RouteAggregationRuleId = types.StringValue(routeAggregationRule.GetUuid()) + m.ID = types.StringValue(routeAggregationRule.GetUuid()) + + diags := parseRouteAggregationRule(ctx, routeAggregationRule, + &m.Name, + &m.Description, + &m.Prefix, + &m.Href, + &m.Type, + &m.Uuid, + &m.State, + &m.Change, + &m.ChangeLog) + if diags.HasError() { + return diags + } + return diags +} + +func (m *DatsSourceAllRouteAggregationRulesModel) parse(ctx context.Context, routeAggregationRulesResponse *fabricv4.GetRouteAggregationRulesResponse) diag.Diagnostics { + var diags diag.Diagnostics + + if len(routeAggregationRulesResponse.GetData()) < 1 { + diags.AddError("no data retrieved by route aggregation rules data source", "either the account does not have any route aggregation rules data to pull or the combination of limit and offset needs to be updated") + return diags + } + + data := make([]BaseRouteAggregationRuleModel, len(routeAggregationRulesResponse.GetData())) + routeAggregationRules := routeAggregationRulesResponse.GetData() + for index, routeAggregationRule := range routeAggregationRules { + var routeAggregationRuleModel BaseRouteAggregationRuleModel + diags = routeAggregationRuleModel.parse(ctx, &routeAggregationRule) + if diags.HasError() { + return diags + } + data[index] = routeAggregationRuleModel + } + responsePagination := routeAggregationRulesResponse.GetPagination() + pagination := PaginationModel{ + Offset: types.Int32Value(responsePagination.GetOffset()), + Limit: types.Int32Value(responsePagination.GetLimit()), + Total: types.Int32Value(responsePagination.GetTotal()), + Next: types.StringValue(responsePagination.GetNext()), + Previous: types.StringValue(responsePagination.GetPrevious()), + } + m.ID = types.StringValue(data[0].Uuid.ValueString()) + m.Pagination = fwtypes.NewObjectValueOf[PaginationModel](ctx, &pagination) + + dataPtr := make([]*BaseRouteAggregationRuleModel, len(data)) + for i := range data { + dataPtr[i] = &data[i] + } + m.Data = fwtypes.NewListNestedObjectValueOfSlice[BaseRouteAggregationRuleModel](ctx, dataPtr) + + return diags +} + +func (m *ResourceModel) parse(ctx context.Context, routeAggregationRule *fabricv4.RouteAggregationRulesData) diag.Diagnostics { + var diags diag.Diagnostics + + m.ID = types.StringValue(routeAggregationRule.GetUuid()) + + diags = parseRouteAggregationRule(ctx, routeAggregationRule, + &m.Name, + &m.Description, + &m.Prefix, + &m.Href, + &m.Type, + &m.Uuid, + &m.State, + &m.Change, + &m.ChangeLog) + if diags.HasError() { + return diags + } + return diags +} + +func (m *BaseRouteAggregationRuleModel) parse(ctx context.Context, routeAggregationRule *fabricv4.RouteAggregationRulesData) diag.Diagnostics { + var diags diag.Diagnostics = parseRouteAggregationRule(ctx, routeAggregationRule, + &m.Name, + &m.Description, + &m.Prefix, + &m.Href, + &m.Type, + &m.Uuid, + &m.State, + &m.Change, + &m.ChangeLog) + if diags.HasError() { + return diags + } + return diags +} + +func parseRouteAggregationRule(ctx context.Context, routeAggregationRule *fabricv4.RouteAggregationRulesData, + name, description, prefix, href, type_, uuid, state *basetypes.StringValue, + change *fwtypes.ObjectValueOf[ChangeModel], + changeLog *fwtypes.ObjectValueOf[ChangeLogModel]) diag.Diagnostics { + var diag diag.Diagnostics + + *name = types.StringValue(routeAggregationRule.GetName()) + *description = types.StringValue(routeAggregationRule.GetDescription()) + *prefix = types.StringValue(routeAggregationRule.GetPrefix()) + *href = types.StringValue(routeAggregationRule.GetHref()) + *type_ = types.StringValue(string(routeAggregationRule.GetType())) + *uuid = types.StringValue(routeAggregationRule.GetUuid()) + *state = types.StringValue(string(routeAggregationRule.GetState())) + + routeAggregationRuleChange := routeAggregationRule.GetChange() + changeModel := ChangeModel{ + Uuid: types.StringValue(routeAggregationRuleChange.GetUuid()), + Type: types.StringValue(string(routeAggregationRuleChange.GetType())), + Href: types.StringValue(routeAggregationRuleChange.GetHref()), + } + *change = fwtypes.NewObjectValueOf[ChangeModel](ctx, &changeModel) + + const TIMEFORMAT = "2008-02-02T14:02:02.000Z" + routeAggregationRuleChangeLog := routeAggregationRule.GetChangeLog() + changeLogModel := ChangeLogModel{ + CreatedBy: types.StringValue(routeAggregationRuleChangeLog.GetCreatedBy()), + CreatedByFullName: types.StringValue(routeAggregationRuleChangeLog.GetCreatedByFullName()), + CreatedByEmail: types.StringValue(routeAggregationRuleChangeLog.GetCreatedByEmail()), + CreatedDateTime: types.StringValue(routeAggregationRuleChangeLog.GetCreatedDateTime().Format(TIMEFORMAT)), + UpdatedBy: types.StringValue(routeAggregationRuleChangeLog.GetUpdatedBy()), + UpdatedByFullName: types.StringValue(routeAggregationRuleChangeLog.GetUpdatedByFullName()), + UpdatedByEmail: types.StringValue(routeAggregationRuleChangeLog.GetUpdatedByEmail()), + UpdatedDateTime: types.StringValue(routeAggregationRuleChangeLog.GetUpdatedDateTime().Format(TIMEFORMAT)), + DeletedBy: types.StringValue(routeAggregationRuleChangeLog.GetDeletedBy()), + DeletedByFullName: types.StringValue(routeAggregationRuleChangeLog.GetDeletedByFullName()), + DeletedByEmail: types.StringValue(routeAggregationRuleChangeLog.GetDeletedByEmail()), + DeletedDateTime: types.StringValue(routeAggregationRuleChangeLog.GetDeletedDateTime().Format(TIMEFORMAT)), + } + *changeLog = fwtypes.NewObjectValueOf[ChangeLogModel](ctx, &changeLogModel) + return diag +} diff --git a/internal/resources/fabric/route_aggregation_rule/resource.go b/internal/resources/fabric/route_aggregation_rule/resource.go new file mode 100644 index 000000000..5edd9c5b7 --- /dev/null +++ b/internal/resources/fabric/route_aggregation_rule/resource.go @@ -0,0 +1,288 @@ +package route_aggregation_rule + +import ( + "context" + "fmt" + "github.com/equinix/equinix-sdk-go/services/fabricv4" + equinix_errors "github.com/equinix/terraform-provider-equinix/internal/errors" + "github.com/equinix/terraform-provider-equinix/internal/framework" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "net/http" + "slices" + "strings" + "time" +) + +func NewResource() resource.Resource { + return &Resource{ + BaseResource: framework.NewBaseResource( + framework.BaseResourceConfig{ + Name: "equinix_fabric_route_aggregation_rule", + }, + ), + } +} + +type Resource struct { + framework.BaseResource +} + +func (r Resource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = resourceSchema(ctx) +} + +func (r *Resource) Create( + ctx context.Context, + req resource.CreateRequest, + resp *resource.CreateResponse, +) { + var plan ResourceModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + client := r.Meta.NewFabricClientForFramework(ctx, req.ProviderMeta) + + routeAggregationId := plan.RouteAggregationID.ValueString() + + createRequest, diags := buildCreateRequest(ctx, plan) + if diags.HasError() { + return + } + + routeAggregationRule, _, err := client.RouteAggregationRulesApi.CreateRouteAggregationRule(ctx, routeAggregationId).RouteAggregationRulesBase(createRequest).Execute() + + if err != nil { + resp.Diagnostics.AddError("Failed creating route aggregation rule", equinix_errors.FormatFabricError(err).Error()) + return + } + + createTimeout, diags := plan.Timeouts.Create(ctx, 10*time.Minute) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + createWaiter := getCreateUpdateWaiter(ctx, client, routeAggregationId, routeAggregationRule.GetUuid(), createTimeout) + routeAggregationRuleChecked, err := createWaiter.WaitForStateContext(ctx) + if err != nil { + resp.Diagnostics.AddError(fmt.Sprintf("Failed creating Route Aggregation %s", routeAggregationRule.GetUuid()), err.Error()) + return + } + + resp.Diagnostics.Append(diags...) + if diags.HasError() { + return + } + + resp.Diagnostics.Append(plan.parse(ctx, routeAggregationRuleChecked.(*fabricv4.RouteAggregationRulesData))...) + if resp.Diagnostics.HasError() { + return + } + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *Resource) Read( + ctx context.Context, + req resource.ReadRequest, + resp *resource.ReadResponse, +) { + var state ResourceModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + //Retrieve the API client from the provider metadata + client := r.Meta.NewFabricClientForFramework(ctx, req.ProviderMeta) + + // Extract the ID of the resource from the state + id := state.ID.ValueString() + routeAggregationId := state.RouteAggregationID.ValueString() + + routeAggregationRule, _, err := client.RouteAggregationRulesApi.GetRouteAggregationRuleByUuid(ctx, routeAggregationId, id).Execute() + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Failed retrieving Route Aggregation Rule %s", id), equinix_errors.FormatFabricError(err).Error()) + return + } + + // Set state to fully populated data + resp.Diagnostics.Append(state.parse(ctx, routeAggregationRule)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *Resource) Update( + ctx context.Context, + req resource.UpdateRequest, + resp *resource.UpdateResponse, +) { + client := r.Meta.NewFabricClientForFramework(ctx, req.ProviderMeta) + + //Retrieve values from plan + var state, plan ResourceModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + id := state.ID.ValueString() + routeAggregationId := state.RouteAggregationID.ValueString() + + newPrefix, oldPrefix := plan.Prefix.ValueString(), state.Prefix.ValueString() + + if newPrefix == oldPrefix { + resp.Diagnostics.AddWarning("No updatable fields have changed", "Terraform detected a config change, but it is for a field that isn't updatable for the route aggregation rule resource. Please revert to prior config") + return + } + + updateRequest := []fabricv4.RouteAggregationRulesPatchRequestItem{{ + Op: "replace", + Path: "/prefix", + Value: map[string]interface{}{"": newPrefix}, + }} + + _, _, err := client.RouteAggregationRulesApi.PatchRouteAggregationRuleByUuid(ctx, routeAggregationId, id).RouteAggregationRulesPatchRequestItem(updateRequest).Execute() + + if err != nil { + resp.Diagnostics.AddError(fmt.Sprintf("Failed updating Route Aggregation %s", id), equinix_errors.FormatFabricError(err).Error()) + return + } + + updateTimeout, diags := plan.Timeouts.Update(ctx, 10*time.Minute) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + updateWaiter := getCreateUpdateWaiter(ctx, client, routeAggregationId, id, updateTimeout) + routeAggregationRuleChecked, err := updateWaiter.WaitForStateContext(ctx) + if err != nil { + resp.Diagnostics.AddError(fmt.Sprintf("Failed updating Route Aggregation Rule%s", id), err.Error()) + return + } + + resp.Diagnostics.Append(plan.parse(ctx, routeAggregationRuleChecked.(*fabricv4.RouteAggregationRulesData))...) + if resp.Diagnostics.HasError() { + return + } + + //Set the updated state back into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *Resource) Delete( + ctx context.Context, + req resource.DeleteRequest, + resp *resource.DeleteResponse, +) { + //Retrieve the API client + client := r.Meta.NewFabricClientForFramework(ctx, req.ProviderMeta) + + //Retrieve the current state + var state ResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + id := state.ID.ValueString() + routeAggregationId := state.RouteAggregationID.ValueString() + _, deleteResp, err := client.RouteAggregationRulesApi.DeleteRouteAggregationRuleByUuid(ctx, routeAggregationId, id).Execute() + + if err != nil { + if deleteResp == nil || !slices.Contains([]int{http.StatusForbidden, http.StatusNotFound}, deleteResp.StatusCode) { + resp.Diagnostics.AddError(fmt.Sprintf("Failed deleting Route Aggregation Rule %s", id), equinix_errors.FormatFabricError(err).Error()) + return + } + } + + deleteTimeout, diags := state.Timeouts.Delete(ctx, 10*time.Minute) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + deletewaiter := getDeleteWaiter(ctx, client, routeAggregationId, id, deleteTimeout) + _, err = deletewaiter.WaitForStateContext(ctx) + + if err != nil { + resp.Diagnostics.AddError(fmt.Sprintf("Failed deleting Route Aggregation Rule %s", id), err.Error()) + return + } +} + +func buildCreateRequest(ctx context.Context, plan ResourceModel) (fabricv4.RouteAggregationRulesBase, diag.Diagnostics) { + var diags diag.Diagnostics + request := fabricv4.RouteAggregationRulesBase{} + + request.SetName(plan.Name.ValueString()) + request.SetDescription(plan.Description.ValueString()) + request.SetPrefix(plan.Prefix.ValueString()) + + return request, diags +} + +func getCreateUpdateWaiter(ctx context.Context, client *fabricv4.APIClient, routeAggregationId string, routeAggregationRuleId string, timeout time.Duration) *retry.StateChangeConf { + return &retry.StateChangeConf{ + Pending: []string{ + string(fabricv4.ROUTEAGGREGATIONRULESTATE_PROVISIONING), + }, + Target: []string{ + string(fabricv4.ROUTEAGGREGATIONRULESTATE_PROVISIONED), + }, + Refresh: func() (interface{}, string, error) { + routeAggregationRule, _, err := client.RouteAggregationRulesApi.GetRouteAggregationRuleByUuid(ctx, routeAggregationId, routeAggregationRuleId).Execute() + if err != nil { + return 0, "", err + } + return routeAggregationRule, string(routeAggregationRule.GetState()), nil + }, + Timeout: timeout, + Delay: 30 * time.Second, + MinTimeout: 30 * time.Second, + } +} + +func getDeleteWaiter(ctx context.Context, client *fabricv4.APIClient, routeAggregationId string, id string, timeout time.Duration) *retry.StateChangeConf { + // deletedMarker is a terraform-provider-only value that is used by the waiter + // to indicate that the resource appears to be deleted successfully based on + // status code or specific error code + deletedMarker := "tf-marker-for-deleted-route-aggregation-rule" + return &retry.StateChangeConf{ + Pending: []string{ + string(fabricv4.ROUTEAGGREGATIONRULESTATE_DEPROVISIONING), + }, + Target: []string{ + deletedMarker, + }, + Refresh: func() (interface{}, string, error) { + routeAggregationRule, resp, err := client.RouteAggregationRulesApi.GetRouteAggregationRuleByUuid(ctx, routeAggregationId, id).Execute() + if err != nil { + if resp != nil { + if slices.Contains([]int{http.StatusForbidden, http.StatusNotFound}, resp.StatusCode) { + return routeAggregationRule, deletedMarker, nil + } + apiError, ok := err.(*fabricv4.GenericOpenAPIError) + if ok { + errorBody := string(apiError.Body()) + if strings.Contains(errorBody, "EQ-3044402") { + return routeAggregationRule, deletedMarker, nil + } + } + } + return 0, "", err + } + return routeAggregationRule, string(routeAggregationRule.GetState()), nil + }, + Timeout: timeout, + Delay: 10 * time.Second, + MinTimeout: 5 * time.Second, + } +} diff --git a/internal/resources/fabric/route_aggregation_rule/resource_schema.go b/internal/resources/fabric/route_aggregation_rule/resource_schema.go new file mode 100644 index 000000000..897ea145d --- /dev/null +++ b/internal/resources/fabric/route_aggregation_rule/resource_schema.go @@ -0,0 +1,138 @@ +package route_aggregation_rule + +import ( + "context" + "github.com/equinix/terraform-provider-equinix/internal/framework" + fwtypes "github.com/equinix/terraform-provider-equinix/internal/framework/types" + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" +) + +func resourceSchema(ctx context.Context) schema.Schema { + return schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": framework.IDAttributeDefaultDescription(), + "timeouts": timeouts.Attributes(ctx, timeouts.Opts{ + Create: true, + Read: true, + Update: true, + Delete: true, + }), + "route_aggregation_id": schema.StringAttribute{ + Description: "UUID of the Route Aggregation to apply this Rule to", + Required: true, + }, + "name": schema.StringAttribute{ + Description: "Customer provided name of the route aggregation rule", + Required: true, + }, + "description": schema.StringAttribute{ + Description: "Customer-provided route aggregation rule description", + Optional: true, + }, + "prefix": schema.StringAttribute{ + Description: "Customer-provided route aggregation rule prefix", + Required: true, + }, + "href": schema.StringAttribute{ + Description: "Equinix auto generated URI to the route aggregation rule resource", + Computed: true, + }, + "type": schema.StringAttribute{ + Description: "Equinix defined Route Aggregation Type; BGP_IPv4_PREFIX_AGGREGATION, BGP_IPv6_PREFIX_AGGREGATION", + Computed: true, + }, + "uuid": schema.StringAttribute{ + Description: "Equinix-assigned unique id for the route aggregation rule resource", + Computed: true, + }, + "state": schema.StringAttribute{ + Description: "Value representing provisioning status for the route aggregation rule resource", + Computed: true, + }, + "change": schema.SingleNestedAttribute{ + Description: "Current state of latest route aggregation rule change", + Computed: true, + PlanModifiers: []planmodifier.Object{ + objectplanmodifier.UseStateForUnknown(), + }, + CustomType: fwtypes.NewObjectTypeOf[ChangeModel](ctx), + Attributes: map[string]schema.Attribute{ + "uuid": schema.StringAttribute{ + Description: "Equinix-assigned unique id for a change", + Required: true, + }, + "type": schema.StringAttribute{ + Description: "Equinix defined Route Aggregation Change Type", + Required: true, + }, + "href": schema.StringAttribute{ + Description: "Equinix auto generated URI to the route aggregation change", + Computed: true, + }, + }, + }, + "change_log": schema.SingleNestedAttribute{ + Description: "Details of the last change on the stream resource", + Computed: true, + PlanModifiers: []planmodifier.Object{ + objectplanmodifier.UseStateForUnknown(), + }, + CustomType: fwtypes.NewObjectTypeOf[ChangeLogModel](ctx), + Attributes: map[string]schema.Attribute{ + "created_by": schema.StringAttribute{ + Description: "User name of creator of the stream resource", + Computed: true, + }, + "created_by_full_name": schema.StringAttribute{ + Description: "Legal name of creator of the stream resource", + Computed: true, + }, + "created_by_email": schema.StringAttribute{ + Description: "Email of creator of the stream resource", + Computed: true, + }, + "created_date_time": schema.StringAttribute{ + Description: "Creation time of the stream resource", + Computed: true, + }, + "updated_by": schema.StringAttribute{ + Description: "User name of last updater of the stream resource", + Computed: true, + }, + "updated_by_full_name": schema.StringAttribute{ + Description: "Legal name of last updater of the stream resource", + Computed: true, + }, + "updated_by_email": schema.StringAttribute{ + Description: "Email of last updater of the stream resource", + Computed: true, + }, + "updated_date_time": schema.StringAttribute{ + Description: "Last update time of the stream resource", + Computed: true, + }, + "deleted_by": schema.StringAttribute{ + Description: "User name of deleter of the stream resource", + Computed: true, + }, + "deleted_by_full_name": schema.StringAttribute{ + Description: "Legal name of deleter of the stream resource", + Computed: true, + }, + "deleted_by_email": schema.StringAttribute{ + Description: "Email of deleter of the stream resource", + Computed: true, + }, + "deleted_date_time": schema.StringAttribute{ + Description: "Deletion time of the stream resource", + Computed: true, + }, + }, + }, + }, + } + +} diff --git a/internal/resources/fabric/route_aggregation_rule/resource_test.go b/internal/resources/fabric/route_aggregation_rule/resource_test.go new file mode 100644 index 000000000..94c887bdf --- /dev/null +++ b/internal/resources/fabric/route_aggregation_rule/resource_test.go @@ -0,0 +1,109 @@ +package route_aggregation_rule_test + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "testing" + + "github.com/equinix/equinix-sdk-go/services/fabricv4" + "github.com/equinix/terraform-provider-equinix/internal/config" + + "github.com/equinix/terraform-provider-equinix/internal/acceptance" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +func testAccFabricRouteAggregationRuleConfig(name string) string { + return fmt.Sprintf(` + + resource "equinix_fabric_route_aggregation" "test" { + type = "BGP_IPv4_PREFIX_AGGREGATION" + name = "%s" + description = "Test Route Aggregation" + project = { + project_id = "4f855852-eb47-4721-8e40-b386a3676abf" + } + } + + resource "equinix_fabric_route_aggregation_rule" "test" { + route_aggregation_id = equinix_fabric_route_aggregation.test.id + name = "%s" + description = "Test aggregation rule" + prefix = "192.169.0.0/24" + } + `, name) +} + +func TestAccFabricRouteAggregationRule_PFCR(t *testing.T) { + routeAggregationRuleName := "RouteAggregationRulePFCR" + //upRouteAggregationName := "stream_up_PFCR" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.TestAccPreCheck(t); acceptance.TestAccPreCheckProviderConfigured(t) }, + ExternalProviders: acceptance.TestExternalProviders, + ProtoV6ProviderFactories: acceptance.ProtoV6ProviderFactories, + CheckDestroy: CheckRouteAggregationRuleDelete, + Steps: []resource.TestStep{ + { + Config: testAccFabricRouteAggregationRuleConfig(routeAggregationRuleName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "equinix_fabric_route_aggregation_rule.test", "name", routeAggregationRuleName), + resource.TestCheckResourceAttrSet("equinix_fabric_route_aggregation_rule.test", "uuid"), + resource.TestCheckResourceAttrSet("equinix_fabric_route_aggregation_rule.test", "state"), + resource.TestCheckResourceAttrSet("equinix_fabric_route_aggregation_rule.test", "href"), + resource.TestCheckResourceAttr("equinix_fabric_route_aggregation_rule.test", "name", routeAggregationRuleName), + resource.TestCheckResourceAttr("equinix_fabric_route_aggregation_rule.test", "type", "BGP_IPv4_PREFIX_AGGREGATION_RULE"), + resource.TestCheckResourceAttr("equinix_fabric_route_aggregation_rule.test", "description", "Test aggregation rule"), + ), + ExpectNonEmptyPlan: false, + }, + }, + }) + +} + +func CheckRouteAggregationRuleDelete(s *terraform.State) error { + ctx := context.Background() + client := acceptance.TestAccProvider.Meta().(*config.Config).NewFabricClientForTesting(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "equinix_fabric_route_aggregation_rule" { + continue + } + + routeAggregationId := rs.Primary.Attributes["route_aggregation_id"] + _, resp, err := client.RouteAggregationRulesApi.GetRouteAggregationRuleByUuid(ctx, routeAggregationId, rs.Primary.ID).Execute() + if err != nil { + // Check if the response exists and contains status 400 or 404 + if resp != nil && (resp.StatusCode == 400 || resp.StatusCode == 404) { + fmt.Printf("Resource %s not found, treating as deleted\n", rs.Primary.ID) + return nil + } + + // Handle specific API error messages + var apiErr *fabricv4.GenericOpenAPIError + if errors.As(err, &apiErr) { + errorBody := apiErr.Body() + var errorResponse map[string]interface{} + if jsonErr := json.Unmarshal(errorBody, &errorResponse); jsonErr == nil { + if errorCode, exists := errorResponse["errorCode"]; exists && errorCode == "EQ-3044402" { + fmt.Printf("Detected EQ-3044402 for resource %s, treating as deleted\n", rs.Primary.ID) + return nil // Successfully handled the expected deletion case + } + } + } + + return fmt.Errorf("unexpected API error checking deletion: %v", err) + } + + if routeAggregationRule, _, err := client.RouteAggregationRulesApi.GetRouteAggregationRuleByUuid(ctx, routeAggregationId, rs.Primary.ID).Execute(); err == nil { + if routeAggregationRule.GetState() == fabricv4.ROUTEAGGREGATIONRULESTATE_PROVISIONED { + return fmt.Errorf("fabric stream %s still exists and is %s", + rs.Primary.ID, routeAggregationRule.GetState()) + } + } + } + return nil +} From 51513724790658df76d850e88f89f65918994b91 Mon Sep 17 00:00:00 2001 From: srushti-patl Date: Tue, 25 Feb 2025 15:25:40 -0800 Subject: [PATCH 2/4] fix: Resolving lint errors --- .../fabric_route_aggregation_rule.md | 3 + internal/provider/services/fabric.go | 16 +- .../datasource_test.go | 59 -------- ...asource_all_connectionRouteAggregations.go | 11 +- ...asource_by_connectionRouteAggregationId.go | 13 +- .../datasource_schema.go | 9 +- .../datasource_test.go | 3 + .../models.go | 95 ++++-------- .../resource.go | 57 ++++---- .../resource_schema.go | 3 +- .../resource_test.go | 76 ++++++++-- .../datasource_all_routeAggregationRules.go | 32 ++-- .../datasource_by_routeAggregationRuleId.go | 12 +- .../datasource_schema.go | 11 +- .../datasource_test.go | 31 ++-- .../models.go | 138 ++++++------------ .../resource.go | 61 ++++---- .../resource_schema.go | 19 +-- .../resource_test.go | 49 ++++--- 19 files changed, 317 insertions(+), 381 deletions(-) delete mode 100644 internal/resources/fabric/connection_route_aggregation/datasource_test.go rename internal/resources/fabric/{connection_route_aggregation => connectionrouteaggregation}/datasource_all_connectionRouteAggregations.go (87%) rename internal/resources/fabric/{connection_route_aggregation => connectionrouteaggregation}/datasource_by_connectionRouteAggregationId.go (84%) rename internal/resources/fabric/{connection_route_aggregation => connectionrouteaggregation}/datasource_schema.go (94%) create mode 100644 internal/resources/fabric/connectionrouteaggregation/datasource_test.go rename internal/resources/fabric/{connection_route_aggregation => connectionrouteaggregation}/models.go (51%) rename internal/resources/fabric/{connection_route_aggregation => connectionrouteaggregation}/resource.go (79%) rename internal/resources/fabric/{connection_route_aggregation => connectionrouteaggregation}/resource_schema.go (97%) rename internal/resources/fabric/{connection_route_aggregation => connectionrouteaggregation}/resource_test.go (55%) rename internal/resources/fabric/{route_aggregation_rule => routeaggregationrule}/datasource_all_routeAggregationRules.go (67%) rename internal/resources/fabric/{route_aggregation_rule => routeaggregationrule}/datasource_by_routeAggregationRuleId.go (84%) rename internal/resources/fabric/{route_aggregation_rule => routeaggregationrule}/datasource_schema.go (96%) rename internal/resources/fabric/{route_aggregation_rule => routeaggregationrule}/datasource_test.go (80%) rename internal/resources/fabric/{route_aggregation_rule => routeaggregationrule}/models.go (61%) rename internal/resources/fabric/{route_aggregation_rule => routeaggregationrule}/resource.go (84%) rename internal/resources/fabric/{route_aggregation_rule => routeaggregationrule}/resource_schema.go (89%) rename internal/resources/fabric/{route_aggregation_rule => routeaggregationrule}/resource_test.go (69%) diff --git a/docs/resources/fabric_route_aggregation_rule.md b/docs/resources/fabric_route_aggregation_rule.md index 864e1d7ed..6244530d5 100644 --- a/docs/resources/fabric_route_aggregation_rule.md +++ b/docs/resources/fabric_route_aggregation_rule.md @@ -4,7 +4,10 @@ subcategory: "Fabric" # equinix_fabric_route_aggregation_rule (Resource) +Fabric V4 API compatible resource allows creation and management of Equinix Fabric Route Aggregation +Additional Documentation: +* API: https://developer.equinix.com/catalog/fabricv4#tag/Route-Aggregations ## Example Usage diff --git a/internal/provider/services/fabric.go b/internal/provider/services/fabric.go index fe897940e..334cb067d 100644 --- a/internal/provider/services/fabric.go +++ b/internal/provider/services/fabric.go @@ -1,10 +1,10 @@ package services import ( - "github.com/equinix/terraform-provider-equinix/internal/resources/fabric/connection_route_aggregation" + "github.com/equinix/terraform-provider-equinix/internal/resources/fabric/connectionrouteaggregation" "github.com/equinix/terraform-provider-equinix/internal/resources/fabric/metro" - "github.com/equinix/terraform-provider-equinix/internal/resources/fabric/route_aggregation_rule" "github.com/equinix/terraform-provider-equinix/internal/resources/fabric/routeaggregation" + "github.com/equinix/terraform-provider-equinix/internal/resources/fabric/routeaggregationrule" "github.com/equinix/terraform-provider-equinix/internal/resources/fabric/stream" streamattachment "github.com/equinix/terraform-provider-equinix/internal/resources/fabric/stream_attachment" @@ -14,9 +14,9 @@ import ( func FabricResources() []func() resource.Resource { return []func() resource.Resource{ - connection_route_aggregation.NewResource, + connectionrouteaggregation.NewResource, routeaggregation.NewResource, - route_aggregation_rule.NewResource, + routeaggregationrule.NewResource, stream.NewResource, streamattachment.NewResource, } @@ -24,14 +24,14 @@ func FabricResources() []func() resource.Resource { func FabricDatasources() []func() datasource.DataSource { return []func() datasource.DataSource{ - connection_route_aggregation.NewDataSourceByConnectionRouteAggregationID, - connection_route_aggregation.NewDataSourceAllConnectionRouteAggregations, + connectionrouteaggregation.NewDataSourceByConnectionRouteAggregationID, + connectionrouteaggregation.NewDataSourceAllConnectionRouteAggregations, metro.NewDataSourceMetroCode, metro.NewDataSourceMetros, routeaggregation.NewDataSourceByRouteAggregationID, routeaggregation.NewDataSourceAllRouteAggregation, - route_aggregation_rule.NewDataSourceByRouteAggregationRuleID, - route_aggregation_rule.NewDataSourceAllRouteAggregationRule, + routeaggregationrule.NewDataSourceByRouteAggregationRuleID, + routeaggregationrule.NewDataSourceAllRouteAggregationRule, stream.NewDataSourceByStreamID, stream.NewDataSourceAllStreams, streamattachment.NewDataSourceAllStreamAttachments, diff --git a/internal/resources/fabric/connection_route_aggregation/datasource_test.go b/internal/resources/fabric/connection_route_aggregation/datasource_test.go deleted file mode 100644 index 20fafa39c..000000000 --- a/internal/resources/fabric/connection_route_aggregation/datasource_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package connection_route_aggregation_test - -import ( - "fmt" - "github.com/equinix/terraform-provider-equinix/internal/acceptance" - "github.com/hashicorp/terraform-plugin-testing/helper/resource" - "testing" -) - -func testAccFabricConnectionRouteAggregationDataSourcesConfig() string { - return fmt.Sprintf(` - resource "equinix_fabric_connection_route_aggregation" "test" { - route_aggregation_id = "f264c892-bfd4-4823-ab8f-7ee74cc7a0f0" - connection_id = "daa424a5-e7b1-4fb6-bdbc-200e9b4757d1" - } - - data "equinix_fabric_connection_route_aggregation" "data_cra" { - depends_on = [equinix_fabric_connection_route_aggregation.test] - route_aggregation_id = "f264c892-bfd4-4823-ab8f-7ee74cc7a0f0" - connection_id = "daa424a5-e7b1-4fb6-bdbc-200e9b4757d1" - } - - - data "equinix_fabric_connection_route_aggregations" "data_cras" { - depends_on = [equinix_fabric_connection_route_aggregation.test] - connection_id = "daa424a5-e7b1-4fb6-bdbc-200e9b4757d1" - }`) -} - -func TestAccFabricConnectionRouteAggregationDataSources_PFCR(t *testing.T) { - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acceptance.TestAccPreCheck(t); acceptance.TestAccPreCheckProviderConfigured(t) }, - ExternalProviders: acceptance.TestExternalProviders, - ProtoV6ProviderFactories: acceptance.ProtoV6ProviderFactories, - CheckDestroy: CheckConnectionRouteAggregationDelete, - Steps: []resource.TestStep{ - { - Config: testAccFabricConnectionRouteAggregationDataSourcesConfig(), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "data.equinix_fabric_connection_route_aggregation.data_cra", "attachment_status", "ATTACHED"), - resource.TestCheckResourceAttr( - "data.equinix_fabric_connection_route_aggregation.data_cra", "type", "BGP_IPv4_PREFIX_AGGREGATION"), - resource.TestCheckResourceAttrSet("data.equinix_fabric_connection_route_aggregation.data_cra", "href"), - resource.TestCheckResourceAttrSet("data.equinix_fabric_connection_route_aggregation.data_cra", "uuid"), - resource.TestCheckResourceAttrSet("data.equinix_fabric_connection_route_aggregations.data_cras", "data.0.attachment_status"), - resource.TestCheckResourceAttrSet("data.equinix_fabric_connection_route_aggregations.data_cras", "data.0.type"), - resource.TestCheckResourceAttrSet("data.equinix_fabric_connection_route_aggregations.data_cras", "data.0.href"), - resource.TestCheckResourceAttrSet("data.equinix_fabric_connection_route_aggregations.data_cras", "data.0.uuid"), - resource.TestCheckResourceAttr("data.equinix_fabric_connection_route_aggregations.data_cras", "data.#", "1"), - resource.TestCheckResourceAttr("data.equinix_fabric_connection_route_aggregations.data_cras", "pagination.%", "5"), - resource.TestCheckResourceAttr("data.equinix_fabric_connection_route_aggregations.data_cras", "pagination.limit", "10"), - resource.TestCheckResourceAttr("data.equinix_fabric_connection_route_aggregations.data_cras", "pagination.offset", "0"), - ), - ExpectNonEmptyPlan: false, - }, - }, - }) -} diff --git a/internal/resources/fabric/connection_route_aggregation/datasource_all_connectionRouteAggregations.go b/internal/resources/fabric/connectionrouteaggregation/datasource_all_connectionRouteAggregations.go similarity index 87% rename from internal/resources/fabric/connection_route_aggregation/datasource_all_connectionRouteAggregations.go rename to internal/resources/fabric/connectionrouteaggregation/datasource_all_connectionRouteAggregations.go index 113cb09bb..9e830cc2f 100644 --- a/internal/resources/fabric/connection_route_aggregation/datasource_all_connectionRouteAggregations.go +++ b/internal/resources/fabric/connectionrouteaggregation/datasource_all_connectionRouteAggregations.go @@ -1,7 +1,8 @@ -package connection_route_aggregation +package connectionrouteaggregation import ( "context" + equinix_errors "github.com/equinix/terraform-provider-equinix/internal/errors" "github.com/equinix/terraform-provider-equinix/internal/framework" "github.com/hashicorp/terraform-plugin-framework/datasource" @@ -23,7 +24,7 @@ type DataSourceAllConnectionRouteAggregations struct { func (r *DataSourceAllConnectionRouteAggregations) Schema( ctx context.Context, - req datasource.SchemaRequest, + _ datasource.SchemaRequest, resp *datasource.SchemaResponse, ) { resp.Schema = dataSourceAllConnectionRouteAggregationSchema(ctx) @@ -32,15 +33,15 @@ func (r *DataSourceAllConnectionRouteAggregations) Schema( func (r *DataSourceAllConnectionRouteAggregations) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) { client := r.Meta.NewFabricClientForFramework(ctx, request.ProviderMeta) - var data DatsSourceAllConnectionRouteAggregationModel + var data datsSourceAllConnectionRouteAggregationModel response.Diagnostics.Append(request.Config.Get(ctx, &data)...) if response.Diagnostics.HasError() { return } - connectionId := data.ConnectionId.ValueString() + connectionID := data.ConnectionID.ValueString() - connectionRouteAggregations, _, err := client.RouteAggregationsApi.GetConnectionRouteAggregations(ctx, connectionId).Execute() + connectionRouteAggregations, _, err := client.RouteAggregationsApi.GetConnectionRouteAggregations(ctx, connectionID).Execute() if err != nil { response.State.RemoveResource(ctx) diff --git a/internal/resources/fabric/connection_route_aggregation/datasource_by_connectionRouteAggregationId.go b/internal/resources/fabric/connectionrouteaggregation/datasource_by_connectionRouteAggregationId.go similarity index 84% rename from internal/resources/fabric/connection_route_aggregation/datasource_by_connectionRouteAggregationId.go rename to internal/resources/fabric/connectionrouteaggregation/datasource_by_connectionRouteAggregationId.go index e87de3303..00f6f434d 100644 --- a/internal/resources/fabric/connection_route_aggregation/datasource_by_connectionRouteAggregationId.go +++ b/internal/resources/fabric/connectionrouteaggregation/datasource_by_connectionRouteAggregationId.go @@ -1,7 +1,8 @@ -package connection_route_aggregation +package connectionrouteaggregation import ( "context" + equinix_errors "github.com/equinix/terraform-provider-equinix/internal/errors" "github.com/equinix/terraform-provider-equinix/internal/framework" "github.com/hashicorp/terraform-plugin-framework/datasource" @@ -23,7 +24,7 @@ type DataSourceByConnectionRouteAggregationID struct { func (r *DataSourceByConnectionRouteAggregationID) Schema( ctx context.Context, - req datasource.SchemaRequest, + _ datasource.SchemaRequest, resp *datasource.SchemaResponse, ) { resp.Schema = dataSourceSingleConnectionRouteAggregationSchema(ctx) @@ -32,16 +33,16 @@ func (r *DataSourceByConnectionRouteAggregationID) Schema( func (r *DataSourceByConnectionRouteAggregationID) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) { client := r.Meta.NewFabricClientForFramework(ctx, request.ProviderMeta) - var data DataSourceByIdModel + var data dataSourceByIDModel response.Diagnostics.Append(request.Config.Get(ctx, &data)...) if response.Diagnostics.HasError() { return } - routeAggregationId := data.RouteAggregationId.ValueString() - connectionId := data.ConnectionId.ValueString() + routeAggregationID := data.RouteAggregationID.ValueString() + connectionID := data.ConnectionID.ValueString() - routeAggregation, _, err := client.RouteAggregationsApi.GetConnectionRouteAggregationByUuid(ctx, routeAggregationId, connectionId).Execute() + routeAggregation, _, err := client.RouteAggregationsApi.GetConnectionRouteAggregationByUuid(ctx, routeAggregationID, connectionID).Execute() if err != nil { response.State.RemoveResource(ctx) diff --git a/internal/resources/fabric/connection_route_aggregation/datasource_schema.go b/internal/resources/fabric/connectionrouteaggregation/datasource_schema.go similarity index 94% rename from internal/resources/fabric/connection_route_aggregation/datasource_schema.go rename to internal/resources/fabric/connectionrouteaggregation/datasource_schema.go index ec6f01191..8f38a854c 100644 --- a/internal/resources/fabric/connection_route_aggregation/datasource_schema.go +++ b/internal/resources/fabric/connectionrouteaggregation/datasource_schema.go @@ -1,7 +1,8 @@ -package connection_route_aggregation +package connectionrouteaggregation import ( "context" + "github.com/equinix/terraform-provider-equinix/internal/framework" fwtypes "github.com/equinix/terraform-provider-equinix/internal/framework/types" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" @@ -21,7 +22,7 @@ Additional Documentation: "data": schema.ListNestedAttribute{ Description: "Returned list of connection route aggregation objects", Computed: true, - CustomType: fwtypes.NewListNestedObjectTypeOf[BaseConnectionRouteAggregationModel](ctx), + CustomType: fwtypes.NewListNestedObjectTypeOf[baseConnectionRouteAggregationModel](ctx), NestedObject: schema.NestedAttributeObject{ Attributes: getConnectionRouteAggregationSchema(ctx), }, @@ -29,7 +30,7 @@ Additional Documentation: "pagination": schema.SingleNestedAttribute{ Description: "Pagination details for the returned connection route aggregations list", Optional: true, - CustomType: fwtypes.NewObjectTypeOf[PaginationModel](ctx), + CustomType: fwtypes.NewObjectTypeOf[paginationModel](ctx), Attributes: map[string]schema.Attribute{ "offset": schema.Int32Attribute{ Description: "Index of the first item returned in the response. The default is 0", @@ -81,7 +82,7 @@ Additional Documentation: } } -func getConnectionRouteAggregationSchema(ctx context.Context) map[string]schema.Attribute { +func getConnectionRouteAggregationSchema(_ context.Context) map[string]schema.Attribute { return map[string]schema.Attribute{ "route_aggregation_id": schema.StringAttribute{ Description: "UUID of the Route Aggregation to attach this Connection to", diff --git a/internal/resources/fabric/connectionrouteaggregation/datasource_test.go b/internal/resources/fabric/connectionrouteaggregation/datasource_test.go new file mode 100644 index 000000000..90264d7ed --- /dev/null +++ b/internal/resources/fabric/connectionrouteaggregation/datasource_test.go @@ -0,0 +1,3 @@ +package connectionrouteaggregation_test + +// Tested in resource_test.go because of the heavy resource setup constraints diff --git a/internal/resources/fabric/connection_route_aggregation/models.go b/internal/resources/fabric/connectionrouteaggregation/models.go similarity index 51% rename from internal/resources/fabric/connection_route_aggregation/models.go rename to internal/resources/fabric/connectionrouteaggregation/models.go index 4e02dc152..70ebdd5c1 100644 --- a/internal/resources/fabric/connection_route_aggregation/models.go +++ b/internal/resources/fabric/connectionrouteaggregation/models.go @@ -1,28 +1,28 @@ -package connection_route_aggregation +package connectionrouteaggregation import ( "context" + "github.com/equinix/equinix-sdk-go/services/fabricv4" fwtypes "github.com/equinix/terraform-provider-equinix/internal/framework/types" "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-framework/types/basetypes" ) -type DataSourceByIdModel struct { +type dataSourceByIDModel struct { ID types.String `tfsdk:"id"` - BaseConnectionRouteAggregationModel + baseConnectionRouteAggregationModel } -type DatsSourceAllConnectionRouteAggregationModel struct { +type datsSourceAllConnectionRouteAggregationModel struct { ID types.String `tfsdk:"id"` - ConnectionId types.String `tfsdk:"connection_id"` - Data fwtypes.ListNestedObjectValueOf[BaseConnectionRouteAggregationModel] `tfsdk:"data"` - Pagination fwtypes.ObjectValueOf[PaginationModel] `tfsdk:"pagination"` + ConnectionID types.String `tfsdk:"connection_id"` + Data fwtypes.ListNestedObjectValueOf[baseConnectionRouteAggregationModel] `tfsdk:"data"` + Pagination fwtypes.ObjectValueOf[paginationModel] `tfsdk:"pagination"` } -type PaginationModel struct { +type paginationModel struct { Offset types.Int32 `tfsdk:"offset"` Limit types.Int32 `tfsdk:"limit"` Total types.Int32 `tfsdk:"total"` @@ -30,36 +30,28 @@ type PaginationModel struct { Previous types.String `tfsdk:"previous"` } -type ResourceModel struct { +type resourceModel struct { ID types.String `tfsdk:"id"` Timeouts timeouts.Value `tfsdk:"timeouts"` - BaseConnectionRouteAggregationModel + baseConnectionRouteAggregationModel } -type BaseConnectionRouteAggregationModel struct { - RouteAggregationId types.String `tfsdk:"route_aggregation_id"` - ConnectionId types.String `tfsdk:"connection_id"` +type baseConnectionRouteAggregationModel struct { + RouteAggregationID types.String `tfsdk:"route_aggregation_id"` + ConnectionID types.String `tfsdk:"connection_id"` Href types.String `tfsdk:"href"` Type types.String `tfsdk:"type"` - Uuid types.String `tfsdk:"uuid"` + UUID types.String `tfsdk:"uuid"` AttachmentStatus types.String `tfsdk:"attachment_status"` } -func (m *DataSourceByIdModel) parse(ctx context.Context, connectionRouteAggregation *fabricv4.ConnectionRouteAggregationData) diag.Diagnostics { +func (m *dataSourceByIDModel) parse(ctx context.Context, connectionRouteAggregation *fabricv4.ConnectionRouteAggregationData) diag.Diagnostics { m.ID = types.StringValue(connectionRouteAggregation.GetUuid()) - - diags := parseConnectionRouteAggregation(ctx, connectionRouteAggregation, - &m.Href, - &m.Type, - &m.Uuid, - &m.AttachmentStatus) - if diags.HasError() { - return diags - } + diags := m.baseConnectionRouteAggregationModel.parse(ctx, connectionRouteAggregation) return diags } -func (m *DatsSourceAllConnectionRouteAggregationModel) parse(ctx context.Context, connectionRouteAggregationsResponse *fabricv4.GetAllConnectionRouteAggregationsResponse) diag.Diagnostics { +func (m *datsSourceAllConnectionRouteAggregationModel) parse(ctx context.Context, connectionRouteAggregationsResponse *fabricv4.GetAllConnectionRouteAggregationsResponse) diag.Diagnostics { var diags diag.Diagnostics if len(connectionRouteAggregationsResponse.GetData()) < 1 { @@ -67,10 +59,10 @@ func (m *DatsSourceAllConnectionRouteAggregationModel) parse(ctx context.Context return diags } - data := make([]BaseConnectionRouteAggregationModel, len(connectionRouteAggregationsResponse.GetData())) + data := make([]baseConnectionRouteAggregationModel, len(connectionRouteAggregationsResponse.GetData())) connectionRouteAggregations := connectionRouteAggregationsResponse.GetData() for index, routeAggregationRule := range connectionRouteAggregations { - var connectionRouteAggregationModel BaseConnectionRouteAggregationModel + var connectionRouteAggregationModel baseConnectionRouteAggregationModel diags = connectionRouteAggregationModel.parse(ctx, &routeAggregationRule) if diags.HasError() { return diags @@ -78,61 +70,38 @@ func (m *DatsSourceAllConnectionRouteAggregationModel) parse(ctx context.Context data[index] = connectionRouteAggregationModel } responsePagination := connectionRouteAggregationsResponse.GetPagination() - pagination := PaginationModel{ + pagination := paginationModel{ Offset: types.Int32Value(responsePagination.GetOffset()), Limit: types.Int32Value(responsePagination.GetLimit()), Total: types.Int32Value(responsePagination.GetTotal()), Next: types.StringValue(responsePagination.GetNext()), Previous: types.StringValue(responsePagination.GetPrevious()), } - m.ID = types.StringValue(data[0].Uuid.ValueString()) - m.Pagination = fwtypes.NewObjectValueOf[PaginationModel](ctx, &pagination) + m.ID = types.StringValue(data[0].UUID.ValueString()) + m.Pagination = fwtypes.NewObjectValueOf[paginationModel](ctx, &pagination) - dataPtr := make([]*BaseConnectionRouteAggregationModel, len(data)) + dataPtr := make([]*baseConnectionRouteAggregationModel, len(data)) for i := range data { dataPtr[i] = &data[i] } - m.Data = fwtypes.NewListNestedObjectValueOfSlice[BaseConnectionRouteAggregationModel](ctx, dataPtr) + m.Data = fwtypes.NewListNestedObjectValueOfSlice[baseConnectionRouteAggregationModel](ctx, dataPtr) return diags } -func (m *ResourceModel) parse(ctx context.Context, connectionRouteAggregation *fabricv4.ConnectionRouteAggregationData) diag.Diagnostics { - var diags diag.Diagnostics - +func (m *resourceModel) parse(ctx context.Context, connectionRouteAggregation *fabricv4.ConnectionRouteAggregationData) diag.Diagnostics { m.ID = types.StringValue(connectionRouteAggregation.GetUuid()) - - diags = parseConnectionRouteAggregation(ctx, connectionRouteAggregation, - &m.Href, - &m.Type, - &m.Uuid, - &m.AttachmentStatus) - if diags.HasError() { - return diags - } - return diags -} - -func (m *BaseConnectionRouteAggregationModel) parse(ctx context.Context, connectionRouteAggregation *fabricv4.ConnectionRouteAggregationData) diag.Diagnostics { - var diags diag.Diagnostics = parseConnectionRouteAggregation(ctx, connectionRouteAggregation, - &m.Href, - &m.Type, - &m.Uuid, - &m.AttachmentStatus) - if diags.HasError() { - return diags - } + diags := m.baseConnectionRouteAggregationModel.parse(ctx, connectionRouteAggregation) return diags } -func parseConnectionRouteAggregation(ctx context.Context, connectionRouteAggregation *fabricv4.ConnectionRouteAggregationData, - href, type_, uuid, attachmentStatus *basetypes.StringValue) diag.Diagnostics { +func (m *baseConnectionRouteAggregationModel) parse(_ context.Context, connectionRouteAggregation *fabricv4.ConnectionRouteAggregationData) diag.Diagnostics { var diag diag.Diagnostics - *href = types.StringValue(connectionRouteAggregation.GetHref()) - *type_ = types.StringValue(string(connectionRouteAggregation.GetType())) - *uuid = types.StringValue(connectionRouteAggregation.GetUuid()) - *attachmentStatus = types.StringValue(string(connectionRouteAggregation.GetAttachmentStatus())) + m.Href = types.StringValue(connectionRouteAggregation.GetHref()) + m.Type = types.StringValue(string(connectionRouteAggregation.GetType())) + m.UUID = types.StringValue(connectionRouteAggregation.GetUuid()) + m.AttachmentStatus = types.StringValue(string(connectionRouteAggregation.GetAttachmentStatus())) return diag } diff --git a/internal/resources/fabric/connection_route_aggregation/resource.go b/internal/resources/fabric/connectionrouteaggregation/resource.go similarity index 79% rename from internal/resources/fabric/connection_route_aggregation/resource.go rename to internal/resources/fabric/connectionrouteaggregation/resource.go index 3b04de296..5ea1d1a21 100644 --- a/internal/resources/fabric/connection_route_aggregation/resource.go +++ b/internal/resources/fabric/connectionrouteaggregation/resource.go @@ -1,16 +1,17 @@ -package connection_route_aggregation +package connectionrouteaggregation import ( "context" "fmt" + "net/http" + "slices" + "time" + "github.com/equinix/equinix-sdk-go/services/fabricv4" equinix_errors "github.com/equinix/terraform-provider-equinix/internal/errors" "github.com/equinix/terraform-provider-equinix/internal/framework" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" - "net/http" - "slices" - "time" ) func NewResource() resource.Resource { @@ -27,12 +28,11 @@ type Resource struct { framework.BaseResource } -func (r Resource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { - //TODO implement me - panic("implement me") +func (r Resource) Update(_ context.Context, _ resource.UpdateRequest, _ *resource.UpdateResponse) { + //No Update Method Supported by Connection Route Aggregation } -func (r Resource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { +func (r Resource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = resourceSchema(ctx) } @@ -41,7 +41,7 @@ func (r *Resource) Create( req resource.CreateRequest, resp *resource.CreateResponse, ) { - var plan ResourceModel + var plan resourceModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -50,10 +50,10 @@ func (r *Resource) Create( client := r.Meta.NewFabricClientForFramework(ctx, req.ProviderMeta) - routeAggregationId := plan.RouteAggregationId.ValueString() - connectionId := plan.ConnectionId.ValueString() + routeAggregationID := plan.RouteAggregationID.ValueString() + connectionID := plan.ConnectionID.ValueString() - connectionRouteAggregation, _, err := client.RouteAggregationsApi.AttachConnectionRouteAggregation(ctx, routeAggregationId, connectionId).Execute() + connectionRouteAggregation, _, err := client.RouteAggregationsApi.AttachConnectionRouteAggregation(ctx, routeAggregationID, connectionID).Execute() if err != nil { resp.Diagnostics.AddError("Failed attaching connection to route aggregation", equinix_errors.FormatFabricError(err).Error()) @@ -65,18 +65,13 @@ func (r *Resource) Create( resp.Diagnostics.Append(diags...) return } - createWaiter := getCreateUpdateWaiter(ctx, client, routeAggregationId, connectionId, createTimeout) + createWaiter := getCreateUpdateWaiter(ctx, client, routeAggregationID, connectionID, createTimeout) connectionRouteAggregationChecked, err := createWaiter.WaitForStateContext(ctx) if err != nil { resp.Diagnostics.AddError(fmt.Sprintf("Failed attaching Route Aggregation %s", connectionRouteAggregation.GetUuid()), err.Error()) return } - resp.Diagnostics.Append(diags...) - if diags.HasError() { - return - } - resp.Diagnostics.Append(plan.parse(ctx, connectionRouteAggregationChecked.(*fabricv4.ConnectionRouteAggregationData))...) if resp.Diagnostics.HasError() { return @@ -90,7 +85,7 @@ func (r *Resource) Read( req resource.ReadRequest, resp *resource.ReadResponse, ) { - var state ResourceModel + var state resourceModel diags := req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -100,10 +95,10 @@ func (r *Resource) Read( client := r.Meta.NewFabricClientForFramework(ctx, req.ProviderMeta) id := state.ID.ValueString() - routeAggregationId := state.RouteAggregationId.ValueString() - connectionId := state.ConnectionId.ValueString() + routeAggregationID := state.RouteAggregationID.ValueString() + connectionID := state.ConnectionID.ValueString() - connectionRouteAggregation, _, err := client.RouteAggregationsApi.GetConnectionRouteAggregationByUuid(ctx, routeAggregationId, connectionId).Execute() + connectionRouteAggregation, _, err := client.RouteAggregationsApi.GetConnectionRouteAggregationByUuid(ctx, routeAggregationID, connectionID).Execute() if err != nil { resp.Diagnostics.AddError( fmt.Sprintf("Failed retrieving Connection Route Aggregation Attachment %s", id), equinix_errors.FormatFabricError(err).Error()) @@ -128,17 +123,17 @@ func (r *Resource) Delete( client := r.Meta.NewFabricClientForFramework(ctx, req.ProviderMeta) //Retrieve the current state - var state ResourceModel + var state resourceModel resp.Diagnostics.Append(req.State.Get(ctx, &state)...) if resp.Diagnostics.HasError() { return } id := state.ID.ValueString() - routeAggregationId := state.RouteAggregationId.ValueString() - connectionId := state.ConnectionId.ValueString() + routeAggregationID := state.RouteAggregationID.ValueString() + connectionID := state.ConnectionID.ValueString() - _, deleteResp, err := client.RouteAggregationsApi.DetachConnectionRouteAggregation(ctx, routeAggregationId, connectionId).Execute() + _, deleteResp, err := client.RouteAggregationsApi.DetachConnectionRouteAggregation(ctx, routeAggregationID, connectionID).Execute() if err != nil { if deleteResp == nil || !slices.Contains([]int{http.StatusForbidden, http.StatusNotFound}, deleteResp.StatusCode) { @@ -152,7 +147,7 @@ func (r *Resource) Delete( resp.Diagnostics.Append(diags...) return } - deletewaiter := getDeleteWaiter(ctx, client, routeAggregationId, connectionId, deleteTimeout) + deletewaiter := getDeleteWaiter(ctx, client, routeAggregationID, connectionID, deleteTimeout) _, err = deletewaiter.WaitForStateContext(ctx) if err != nil { @@ -161,7 +156,7 @@ func (r *Resource) Delete( } } -func getCreateUpdateWaiter(ctx context.Context, client *fabricv4.APIClient, routeAggregationId string, connectionId string, timeout time.Duration) *retry.StateChangeConf { +func getCreateUpdateWaiter(ctx context.Context, client *fabricv4.APIClient, routeAggregationID string, connectionID string, timeout time.Duration) *retry.StateChangeConf { return &retry.StateChangeConf{ Pending: []string{ string(fabricv4.CONNECTIONROUTEAGGREGATIONDATAATTACHMENTSTATUS_ATTACHING), @@ -170,7 +165,7 @@ func getCreateUpdateWaiter(ctx context.Context, client *fabricv4.APIClient, rout string(fabricv4.CONNECTIONROUTEAGGREGATIONDATAATTACHMENTSTATUS_ATTACHED), }, Refresh: func() (interface{}, string, error) { - connectionRouteAggregation, _, err := client.RouteAggregationsApi.GetConnectionRouteAggregationByUuid(ctx, routeAggregationId, connectionId).Execute() + connectionRouteAggregation, _, err := client.RouteAggregationsApi.GetConnectionRouteAggregationByUuid(ctx, routeAggregationID, connectionID).Execute() if err != nil { return 0, "", err } @@ -182,7 +177,7 @@ func getCreateUpdateWaiter(ctx context.Context, client *fabricv4.APIClient, rout } } -func getDeleteWaiter(ctx context.Context, client *fabricv4.APIClient, routeAggregationId string, connectionId string, timeout time.Duration) *retry.StateChangeConf { +func getDeleteWaiter(ctx context.Context, client *fabricv4.APIClient, routeAggregationID string, connectionID string, timeout time.Duration) *retry.StateChangeConf { // deletedMarker is a terraform-provider-only value that is used by the waiter // to indicate that the resource appears to be deleted successfully based on // status code or specific error code @@ -196,7 +191,7 @@ func getDeleteWaiter(ctx context.Context, client *fabricv4.APIClient, routeAggre deletedMarker, }, Refresh: func() (interface{}, string, error) { - routeAggregationRule, resp, err := client.RouteAggregationsApi.GetConnectionRouteAggregationByUuid(ctx, routeAggregationId, connectionId).Execute() + routeAggregationRule, resp, err := client.RouteAggregationsApi.GetConnectionRouteAggregationByUuid(ctx, routeAggregationID, connectionID).Execute() if err != nil { if resp != nil && slices.Contains([]int{http.StatusForbidden, http.StatusNotFound}, resp.StatusCode) { return routeAggregationRule, deletedMarker, nil diff --git a/internal/resources/fabric/connection_route_aggregation/resource_schema.go b/internal/resources/fabric/connectionrouteaggregation/resource_schema.go similarity index 97% rename from internal/resources/fabric/connection_route_aggregation/resource_schema.go rename to internal/resources/fabric/connectionrouteaggregation/resource_schema.go index abd79275c..a4d7004c0 100644 --- a/internal/resources/fabric/connection_route_aggregation/resource_schema.go +++ b/internal/resources/fabric/connectionrouteaggregation/resource_schema.go @@ -1,7 +1,8 @@ -package connection_route_aggregation +package connectionrouteaggregation import ( "context" + "github.com/equinix/terraform-provider-equinix/internal/framework" "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" "github.com/hashicorp/terraform-plugin-framework/resource/schema" diff --git a/internal/resources/fabric/connection_route_aggregation/resource_test.go b/internal/resources/fabric/connectionrouteaggregation/resource_test.go similarity index 55% rename from internal/resources/fabric/connection_route_aggregation/resource_test.go rename to internal/resources/fabric/connectionrouteaggregation/resource_test.go index ced82c310..7659b1750 100644 --- a/internal/resources/fabric/connection_route_aggregation/resource_test.go +++ b/internal/resources/fabric/connectionrouteaggregation/resource_test.go @@ -1,14 +1,17 @@ -package connection_route_aggregation_test +package connectionrouteaggregation_test import ( "context" "fmt" + "testing" + + "github.com/equinix/terraform-provider-equinix/internal/fabric/testing_helpers" + "github.com/equinix/equinix-sdk-go/services/fabricv4" "github.com/equinix/terraform-provider-equinix/internal/acceptance" "github.com/equinix/terraform-provider-equinix/internal/config" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" - "testing" ) func testAccFabricConnectionRouteAggregationConfig(portUuid string) string { @@ -82,25 +85,53 @@ func testAccFabricConnectionRouteAggregationConfig(portUuid string) string { } } - //resource "equinix_fabric_route_aggregation" "test" { - // type = "BGP_IPv4_PREFIX_AGGREGATION" - // name = "Route_Aggregation_Test" - // description = "Test Route Aggregation" - // project = { - // project_id = "4f855852-eb47-4721-8e40-b386a3676abf" - // } - //} + resource "equinix_fabric_routing_protocol" "direct" { + connection_uuid = equinix_fabric_connection.test.id + type = "DIRECT" + name = "rp_direct_PFCR" + direct_ipv4{ + equinix_iface_ip = "190.1.1.1/30" + } + direct_ipv6{ + equinix_iface_ip = "190::1:1/126" + } +} + + resource "equinix_fabric_route_aggregation" "test" { + type = "BGP_IPv4_PREFIX_AGGREGATION" + name = "Route_Aggregation_Test" + description = "Test Route Aggregation" + project = { + project_id = "4f855852-eb47-4721-8e40-b386a3676abf" + } + } resource "equinix_fabric_connection_route_aggregation" "test" { - route_aggregation_id = "8f8a2ddb-25f8-416e-ad0a-202a9d2af9e1" + depends_on = [equinix_fabric_routing_protocol.direct] + route_aggregation_id = equinix_fabric_route_aggregation.test.id connection_id = equinix_fabric_connection.test.id } + + data "equinix_fabric_connection_route_aggregation" "data_cra" { + depends_on = [equinix_fabric_connection_route_aggregation.test] + route_aggregation_id = equinix_fabric_route_aggregation.test.id + connection_id = equinix_fabric_connection.test.id + } + + + data "equinix_fabric_connection_route_aggregations" "data_cras" { + depends_on = [equinix_fabric_connection_route_aggregation.test] + connection_id = equinix_fabric_connection.test.id + } `, portUuid) } -func TestAccFabricConnectionRouteAggregation_PFCR(t *testing.T) { - portId := "c5720fcc-4ae6-ae6e-13e0-306a5c00adaf" - //upRouteAggregationName := "stream_up_PFCR" +func TestAccFabricConnectionRouteAggregation_PNFV(t *testing.T) { + ports := testing_helpers.GetFabricEnvPorts(t) + var portUuid string + if len(ports) > 0 { + portUuid = ports["pnfv"]["dot1q"][1].GetUuid() + } resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acceptance.TestAccPreCheck(t); acceptance.TestAccPreCheckProviderConfigured(t) }, ExternalProviders: acceptance.TestExternalProviders, @@ -108,14 +139,27 @@ func TestAccFabricConnectionRouteAggregation_PFCR(t *testing.T) { CheckDestroy: CheckConnectionRouteAggregationDelete, Steps: []resource.TestStep{ { - Config: testAccFabricConnectionRouteAggregationConfig(portId), + Config: testAccFabricConnectionRouteAggregationConfig(portUuid), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttrSet("equinix_fabric_connection_route_aggregation.test", "uuid"), resource.TestCheckResourceAttrSet("equinix_fabric_connection_route_aggregation.test", "attachment_status"), resource.TestCheckResourceAttrSet("equinix_fabric_connection_route_aggregation.test", "href"), resource.TestCheckResourceAttr("equinix_fabric_connection_route_aggregation.test", "type", "BGP_IPv4_PREFIX_AGGREGATION"), + resource.TestCheckResourceAttr( + "data.equinix_fabric_connection_route_aggregation.data_cra", "attachment_status", "ATTACHED"), + resource.TestCheckResourceAttr( + "data.equinix_fabric_connection_route_aggregation.data_cra", "type", "BGP_IPv4_PREFIX_AGGREGATION"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_connection_route_aggregation.data_cra", "href"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_connection_route_aggregation.data_cra", "uuid"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_connection_route_aggregations.data_cras", "data.0.attachment_status"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_connection_route_aggregations.data_cras", "data.0.type"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_connection_route_aggregations.data_cras", "data.0.href"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_connection_route_aggregations.data_cras", "data.0.uuid"), + resource.TestCheckResourceAttr("data.equinix_fabric_connection_route_aggregations.data_cras", "data.#", "1"), + resource.TestCheckResourceAttr("data.equinix_fabric_connection_route_aggregations.data_cras", "pagination.%", "5"), + resource.TestCheckResourceAttr("data.equinix_fabric_connection_route_aggregations.data_cras", "pagination.limit", "10"), + resource.TestCheckResourceAttr("data.equinix_fabric_connection_route_aggregations.data_cras", "pagination.offset", "0"), ), - ExpectNonEmptyPlan: false, }, }, }) diff --git a/internal/resources/fabric/route_aggregation_rule/datasource_all_routeAggregationRules.go b/internal/resources/fabric/routeaggregationrule/datasource_all_routeAggregationRules.go similarity index 67% rename from internal/resources/fabric/route_aggregation_rule/datasource_all_routeAggregationRules.go rename to internal/resources/fabric/routeaggregationrule/datasource_all_routeAggregationRules.go index 1910122de..f5f035b8b 100644 --- a/internal/resources/fabric/route_aggregation_rule/datasource_all_routeAggregationRules.go +++ b/internal/resources/fabric/routeaggregationrule/datasource_all_routeAggregationRules.go @@ -1,7 +1,8 @@ -package route_aggregation_rule +package routeaggregationrule import ( "context" + equinix_errors "github.com/equinix/terraform-provider-equinix/internal/errors" "github.com/equinix/terraform-provider-equinix/internal/framework" "github.com/hashicorp/terraform-plugin-framework/datasource" @@ -24,7 +25,7 @@ type DataSourceAllRouteAggregationRules struct { func (r *DataSourceAllRouteAggregationRules) Schema( ctx context.Context, - req datasource.SchemaRequest, + _ datasource.SchemaRequest, resp *datasource.SchemaResponse, ) { resp.Schema = dataSourceAllRouteAggregationRulesSchema(ctx) @@ -33,26 +34,35 @@ func (r *DataSourceAllRouteAggregationRules) Schema( func (r *DataSourceAllRouteAggregationRules) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) { client := r.Meta.NewFabricClientForFramework(ctx, request.ProviderMeta) - var data DatsSourceAllRouteAggregationRulesModel + var data datsSourceAllRouteAggregationRulesModel response.Diagnostics.Append(request.Config.Get(ctx, &data)...) if response.Diagnostics.HasError() { return } - routeAggregationId := data.RouteAggregationId.ValueString() - - var tfpagination PaginationModel - diags := data.Pagination.As(ctx, &tfpagination, basetypes.ObjectAsOptions{}) - if diags.HasError() { - return + routeAggregationID := data.RouteAggregationID.ValueString() + var tfpagination paginationModel + if !data.Pagination.IsNull() && !data.Pagination.IsUnknown() { + diags := data.Pagination.As(ctx, &tfpagination, basetypes.ObjectAsOptions{}) + if diags.HasError() { + response.Diagnostics.Append(diags...) + return + } } + offset := tfpagination.Offset.ValueInt32() limit := tfpagination.Limit.ValueInt32() if limit == 0 { limit = 20 } - - routeAggregations, _, err := client.RouteAggregationRulesApi.GetRouteAggregationRules(ctx, routeAggregationId).Limit(limit).Offset(offset).Execute() + routeAggregationRequest := client.RouteAggregationRulesApi.GetRouteAggregationRules(ctx, routeAggregationID) + if !tfpagination.Limit.IsNull() { + routeAggregationRequest = routeAggregationRequest.Limit(limit) + } + if !tfpagination.Offset.IsNull() { + routeAggregationRequest = routeAggregationRequest.Offset(offset) + } + routeAggregations, _, err := routeAggregationRequest.Execute() if err != nil { response.State.RemoveResource(ctx) diff --git a/internal/resources/fabric/route_aggregation_rule/datasource_by_routeAggregationRuleId.go b/internal/resources/fabric/routeaggregationrule/datasource_by_routeAggregationRuleId.go similarity index 84% rename from internal/resources/fabric/route_aggregation_rule/datasource_by_routeAggregationRuleId.go rename to internal/resources/fabric/routeaggregationrule/datasource_by_routeAggregationRuleId.go index c423ebc18..479797a0e 100644 --- a/internal/resources/fabric/route_aggregation_rule/datasource_by_routeAggregationRuleId.go +++ b/internal/resources/fabric/routeaggregationrule/datasource_by_routeAggregationRuleId.go @@ -1,4 +1,4 @@ -package route_aggregation_rule +package routeaggregationrule import ( "context" @@ -24,7 +24,7 @@ type DataSourceByRouteAggregationRuleID struct { func (r *DataSourceByRouteAggregationRuleID) Schema( ctx context.Context, - req datasource.SchemaRequest, + _ datasource.SchemaRequest, resp *datasource.SchemaResponse, ) { resp.Schema = dataSourceSingleRouteAggregationRuleSchema(ctx) @@ -33,16 +33,16 @@ func (r *DataSourceByRouteAggregationRuleID) Schema( func (r *DataSourceByRouteAggregationRuleID) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) { client := r.Meta.NewFabricClientForFramework(ctx, request.ProviderMeta) - var data DataSourceByIdModel + var data dataSourceByIDModel response.Diagnostics.Append(request.Config.Get(ctx, &data)...) if response.Diagnostics.HasError() { return } - routeAggregationRuleID := data.RouteAggregationRuleId.ValueString() - routeAggregationId := data.RouteAggregationID.ValueString() + routeAggregationRuleID := data.RouteAggregationRuleID.ValueString() + routeAggregationID := data.RouteAggregationID.ValueString() - routeAggregation, _, err := client.RouteAggregationRulesApi.GetRouteAggregationRuleByUuid(ctx, routeAggregationId, routeAggregationRuleID).Execute() + routeAggregation, _, err := client.RouteAggregationRulesApi.GetRouteAggregationRuleByUuid(ctx, routeAggregationID, routeAggregationRuleID).Execute() if err != nil { response.State.RemoveResource(ctx) diff --git a/internal/resources/fabric/route_aggregation_rule/datasource_schema.go b/internal/resources/fabric/routeaggregationrule/datasource_schema.go similarity index 96% rename from internal/resources/fabric/route_aggregation_rule/datasource_schema.go rename to internal/resources/fabric/routeaggregationrule/datasource_schema.go index e441709a1..f1b649b6e 100644 --- a/internal/resources/fabric/route_aggregation_rule/datasource_schema.go +++ b/internal/resources/fabric/routeaggregationrule/datasource_schema.go @@ -1,7 +1,8 @@ -package route_aggregation_rule +package routeaggregationrule import ( "context" + "github.com/equinix/terraform-provider-equinix/internal/framework" fwtypes "github.com/equinix/terraform-provider-equinix/internal/framework/types" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" @@ -21,7 +22,7 @@ Additional Documentation: "data": schema.ListNestedAttribute{ Description: "Returned list of route aggregation rule objects", Computed: true, - CustomType: fwtypes.NewListNestedObjectTypeOf[BaseRouteAggregationRuleModel](ctx), + CustomType: fwtypes.NewListNestedObjectTypeOf[baseRouteAggregationRuleModel](ctx), NestedObject: schema.NestedAttributeObject{ Attributes: getRouteAggregationRuleSchema(ctx), }, @@ -29,7 +30,7 @@ Additional Documentation: "pagination": schema.SingleNestedAttribute{ Description: "Pagination details for the returned route aggregation rules list", Optional: true, - CustomType: fwtypes.NewObjectTypeOf[PaginationModel](ctx), + CustomType: fwtypes.NewObjectTypeOf[paginationModel](ctx), Attributes: map[string]schema.Attribute{ "offset": schema.Int32Attribute{ Description: "Index of the first item returned in the response. The default is 0", @@ -116,7 +117,7 @@ func getRouteAggregationRuleSchema(ctx context.Context) map[string]schema.Attrib "change": schema.SingleNestedAttribute{ Description: "Current state of latest route aggregation rule change", Computed: true, - CustomType: fwtypes.NewObjectTypeOf[ChangeModel](ctx), + CustomType: fwtypes.NewObjectTypeOf[changeModel](ctx), Attributes: map[string]schema.Attribute{ "uuid": schema.StringAttribute{ Description: "Equinix-assigned unique id for a change", @@ -135,7 +136,7 @@ func getRouteAggregationRuleSchema(ctx context.Context) map[string]schema.Attrib "change_log": schema.SingleNestedAttribute{ Description: "Details of the last change on the stream resource", Computed: true, - CustomType: fwtypes.NewObjectTypeOf[ChangeLogModel](ctx), + CustomType: fwtypes.NewObjectTypeOf[changeLogModel](ctx), Attributes: map[string]schema.Attribute{ "created_by": schema.StringAttribute{ Description: "User name of creator of the stream resource", diff --git a/internal/resources/fabric/route_aggregation_rule/datasource_test.go b/internal/resources/fabric/routeaggregationrule/datasource_test.go similarity index 80% rename from internal/resources/fabric/route_aggregation_rule/datasource_test.go rename to internal/resources/fabric/routeaggregationrule/datasource_test.go index b2e5ad7eb..c5099d2f5 100644 --- a/internal/resources/fabric/route_aggregation_rule/datasource_test.go +++ b/internal/resources/fabric/routeaggregationrule/datasource_test.go @@ -1,4 +1,4 @@ -package route_aggregation_rule_test +package routeaggregationrule_test import ( "fmt" @@ -11,32 +11,41 @@ import ( func testAccFabricRouteAggregationRuleDataSourcesConfig(name, description string) string { return fmt.Sprintf(` - + resource "equinix_fabric_route_aggregation" "test" { + type = "BGP_IPv4_PREFIX_AGGREGATION" + name = "test-aggregation" + description = "Test Route Aggregation" + project = { + project_id = "4f855852-eb47-4721-8e40-b386a3676abf" + } + } + resource "equinix_fabric_route_aggregation_rule" "new-rar" { - route_aggregation_id = "8f8a2ddb-25f8-416e-ad0a-202a9d2af9e1" + route_aggregation_id = equinix_fabric_route_aggregation.test.id name = "%[1]s" description = "%[2]s" prefix = "192.166.0.0/24" } data "equinix_fabric_route_aggregation_rule" "data_rar" { - route_aggregation_id = "8f8a2ddb-25f8-416e-ad0a-202a9d2af9e1" + depends_on = [equinix_fabric_route_aggregation_rule.new-rar] + route_aggregation_id = equinix_fabric_route_aggregation.test.id route_aggregation_rule_id = equinix_fabric_route_aggregation_rule.new-rar.id } data "equinix_fabric_route_aggregation_rules" "data_rars" { - depends_on = [equinix_fabric_route_aggregation_rule.new-rar,] - route_aggregation_id = "8f8a2ddb-25f8-416e-ad0a-202a9d2af9e1" + depends_on = [equinix_fabric_route_aggregation_rule.new-rar] + route_aggregation_id = equinix_fabric_route_aggregation.test.id pagination = { - limit = 2 - offset = 1 + limit = 32 + offset = 0 } } `, name, description) } -func TestAccFabricRouteAggregationRuleDataSources_PFCR(t *testing.T) { +func TestAccFabricRouteAggregationRuleDataSources_PNFV(t *testing.T) { routeAggregationName := "route_agg_rule_PFCR" routeAggregatioDescription := "route aggregation rule PFCR" resource.ParallelTest(t, resource.TestCase{ @@ -68,8 +77,8 @@ func TestAccFabricRouteAggregationRuleDataSources_PFCR(t *testing.T) { resource.TestCheckResourceAttrSet("data.equinix_fabric_route_aggregation_rules.data_rars", "data.0.change_log.created_by"), resource.TestCheckResourceAttr("data.equinix_fabric_route_aggregation_rules.data_rars", "data.#", "1"), resource.TestCheckResourceAttr("data.equinix_fabric_route_aggregation_rules.data_rars", "pagination.%", "5"), - resource.TestCheckResourceAttr("data.equinix_fabric_route_aggregation_rules.data_rars", "pagination.limit", "2"), - resource.TestCheckResourceAttr("data.equinix_fabric_route_aggregation_rules.data_rars", "pagination.offset", "1"), + resource.TestCheckResourceAttr("data.equinix_fabric_route_aggregation_rules.data_rars", "pagination.limit", "32"), + resource.TestCheckResourceAttr("data.equinix_fabric_route_aggregation_rules.data_rars", "pagination.offset", "0"), ), ExpectNonEmptyPlan: false, }, diff --git a/internal/resources/fabric/route_aggregation_rule/models.go b/internal/resources/fabric/routeaggregationrule/models.go similarity index 61% rename from internal/resources/fabric/route_aggregation_rule/models.go rename to internal/resources/fabric/routeaggregationrule/models.go index 6808912ef..62dabca5b 100644 --- a/internal/resources/fabric/route_aggregation_rule/models.go +++ b/internal/resources/fabric/routeaggregationrule/models.go @@ -1,29 +1,29 @@ -package route_aggregation_rule +package routeaggregationrule import ( "context" + "github.com/equinix/equinix-sdk-go/services/fabricv4" fwtypes "github.com/equinix/terraform-provider-equinix/internal/framework/types" "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-framework/types/basetypes" ) -type DataSourceByIdModel struct { - RouteAggregationRuleId types.String `tfsdk:"route_aggregation_rule_id"` +type dataSourceByIDModel struct { + RouteAggregationRuleID types.String `tfsdk:"route_aggregation_rule_id"` ID types.String `tfsdk:"id"` - BaseRouteAggregationRuleModel + baseRouteAggregationRuleModel } -type DatsSourceAllRouteAggregationRulesModel struct { +type datsSourceAllRouteAggregationRulesModel struct { ID types.String `tfsdk:"id"` - Data fwtypes.ListNestedObjectValueOf[BaseRouteAggregationRuleModel] `tfsdk:"data"` - Pagination fwtypes.ObjectValueOf[PaginationModel] `tfsdk:"pagination"` - RouteAggregationId types.String `tfsdk:"route_aggregation_id"` + Data fwtypes.ListNestedObjectValueOf[baseRouteAggregationRuleModel] `tfsdk:"data"` + Pagination fwtypes.ObjectValueOf[paginationModel] `tfsdk:"pagination"` + RouteAggregationID types.String `tfsdk:"route_aggregation_id"` } -type PaginationModel struct { +type paginationModel struct { Offset types.Int32 `tfsdk:"offset"` Limit types.Int32 `tfsdk:"limit"` Total types.Int32 `tfsdk:"total"` @@ -31,32 +31,32 @@ type PaginationModel struct { Previous types.String `tfsdk:"previous"` } -type ResourceModel struct { +type resourceModel struct { ID types.String `tfsdk:"id"` Timeouts timeouts.Value `tfsdk:"timeouts"` - BaseRouteAggregationRuleModel + baseRouteAggregationRuleModel } -type BaseRouteAggregationRuleModel struct { +type baseRouteAggregationRuleModel struct { RouteAggregationID types.String `tfsdk:"route_aggregation_id"` Name types.String `tfsdk:"name"` Description types.String `tfsdk:"description"` Prefix types.String `tfsdk:"prefix"` Href types.String `tfsdk:"href"` Type types.String `tfsdk:"type"` - Uuid types.String `tfsdk:"uuid"` + UUID types.String `tfsdk:"uuid"` State types.String `tfsdk:"state"` - Change fwtypes.ObjectValueOf[ChangeModel] `tfsdk:"change"` - ChangeLog fwtypes.ObjectValueOf[ChangeLogModel] `tfsdk:"change_log"` + Change fwtypes.ObjectValueOf[changeModel] `tfsdk:"change"` + ChangeLog fwtypes.ObjectValueOf[changeLogModel] `tfsdk:"change_log"` } -type ChangeModel struct { - Uuid types.String `tfsdk:"uuid"` +type changeModel struct { + UUID types.String `tfsdk:"uuid"` Type types.String `tfsdk:"type"` Href types.String `tfsdk:"href"` } -type ChangeLogModel struct { +type changeLogModel struct { CreatedBy types.String `tfsdk:"created_by"` CreatedByFullName types.String `tfsdk:"created_by_full_name"` CreatedByEmail types.String `tfsdk:"created_by_email"` @@ -71,27 +71,14 @@ type ChangeLogModel struct { DeletedDateTime types.String `tfsdk:"deleted_date_time"` } -func (m *DataSourceByIdModel) parse(ctx context.Context, routeAggregationRule *fabricv4.RouteAggregationRulesData) diag.Diagnostics { - m.RouteAggregationRuleId = types.StringValue(routeAggregationRule.GetUuid()) +func (m *dataSourceByIDModel) parse(ctx context.Context, routeAggregationRule *fabricv4.RouteAggregationRulesData) diag.Diagnostics { + m.RouteAggregationRuleID = types.StringValue(routeAggregationRule.GetUuid()) m.ID = types.StringValue(routeAggregationRule.GetUuid()) - - diags := parseRouteAggregationRule(ctx, routeAggregationRule, - &m.Name, - &m.Description, - &m.Prefix, - &m.Href, - &m.Type, - &m.Uuid, - &m.State, - &m.Change, - &m.ChangeLog) - if diags.HasError() { - return diags - } + diags := m.baseRouteAggregationRuleModel.parse(ctx, routeAggregationRule) return diags } -func (m *DatsSourceAllRouteAggregationRulesModel) parse(ctx context.Context, routeAggregationRulesResponse *fabricv4.GetRouteAggregationRulesResponse) diag.Diagnostics { +func (m *datsSourceAllRouteAggregationRulesModel) parse(ctx context.Context, routeAggregationRulesResponse *fabricv4.GetRouteAggregationRulesResponse) diag.Diagnostics { var diags diag.Diagnostics if len(routeAggregationRulesResponse.GetData()) < 1 { @@ -99,10 +86,10 @@ func (m *DatsSourceAllRouteAggregationRulesModel) parse(ctx context.Context, rou return diags } - data := make([]BaseRouteAggregationRuleModel, len(routeAggregationRulesResponse.GetData())) + data := make([]baseRouteAggregationRuleModel, len(routeAggregationRulesResponse.GetData())) routeAggregationRules := routeAggregationRulesResponse.GetData() for index, routeAggregationRule := range routeAggregationRules { - var routeAggregationRuleModel BaseRouteAggregationRuleModel + var routeAggregationRuleModel baseRouteAggregationRuleModel diags = routeAggregationRuleModel.parse(ctx, &routeAggregationRule) if diags.HasError() { return diags @@ -110,88 +97,54 @@ func (m *DatsSourceAllRouteAggregationRulesModel) parse(ctx context.Context, rou data[index] = routeAggregationRuleModel } responsePagination := routeAggregationRulesResponse.GetPagination() - pagination := PaginationModel{ + pagination := paginationModel{ Offset: types.Int32Value(responsePagination.GetOffset()), Limit: types.Int32Value(responsePagination.GetLimit()), Total: types.Int32Value(responsePagination.GetTotal()), Next: types.StringValue(responsePagination.GetNext()), Previous: types.StringValue(responsePagination.GetPrevious()), } - m.ID = types.StringValue(data[0].Uuid.ValueString()) - m.Pagination = fwtypes.NewObjectValueOf[PaginationModel](ctx, &pagination) + m.ID = types.StringValue(data[0].UUID.ValueString()) + m.Pagination = fwtypes.NewObjectValueOf[paginationModel](ctx, &pagination) - dataPtr := make([]*BaseRouteAggregationRuleModel, len(data)) + dataPtr := make([]*baseRouteAggregationRuleModel, len(data)) for i := range data { dataPtr[i] = &data[i] } - m.Data = fwtypes.NewListNestedObjectValueOfSlice[BaseRouteAggregationRuleModel](ctx, dataPtr) + m.Data = fwtypes.NewListNestedObjectValueOfSlice[baseRouteAggregationRuleModel](ctx, dataPtr) return diags } -func (m *ResourceModel) parse(ctx context.Context, routeAggregationRule *fabricv4.RouteAggregationRulesData) diag.Diagnostics { - var diags diag.Diagnostics +func (m *resourceModel) parse(ctx context.Context, routeAggregationRule *fabricv4.RouteAggregationRulesData) diag.Diagnostics { m.ID = types.StringValue(routeAggregationRule.GetUuid()) - diags = parseRouteAggregationRule(ctx, routeAggregationRule, - &m.Name, - &m.Description, - &m.Prefix, - &m.Href, - &m.Type, - &m.Uuid, - &m.State, - &m.Change, - &m.ChangeLog) - if diags.HasError() { - return diags - } + diags := m.baseRouteAggregationRuleModel.parse(ctx, routeAggregationRule) return diags } -func (m *BaseRouteAggregationRuleModel) parse(ctx context.Context, routeAggregationRule *fabricv4.RouteAggregationRulesData) diag.Diagnostics { - var diags diag.Diagnostics = parseRouteAggregationRule(ctx, routeAggregationRule, - &m.Name, - &m.Description, - &m.Prefix, - &m.Href, - &m.Type, - &m.Uuid, - &m.State, - &m.Change, - &m.ChangeLog) - if diags.HasError() { - return diags - } - return diags -} +func (m *baseRouteAggregationRuleModel) parse(ctx context.Context, routeAggregationRule *fabricv4.RouteAggregationRulesData) diag.Diagnostics { -func parseRouteAggregationRule(ctx context.Context, routeAggregationRule *fabricv4.RouteAggregationRulesData, - name, description, prefix, href, type_, uuid, state *basetypes.StringValue, - change *fwtypes.ObjectValueOf[ChangeModel], - changeLog *fwtypes.ObjectValueOf[ChangeLogModel]) diag.Diagnostics { var diag diag.Diagnostics - - *name = types.StringValue(routeAggregationRule.GetName()) - *description = types.StringValue(routeAggregationRule.GetDescription()) - *prefix = types.StringValue(routeAggregationRule.GetPrefix()) - *href = types.StringValue(routeAggregationRule.GetHref()) - *type_ = types.StringValue(string(routeAggregationRule.GetType())) - *uuid = types.StringValue(routeAggregationRule.GetUuid()) - *state = types.StringValue(string(routeAggregationRule.GetState())) - + m.Name = types.StringValue(routeAggregationRule.GetName()) + m.Description = types.StringValue(routeAggregationRule.GetDescription()) + m.Prefix = types.StringValue(routeAggregationRule.GetPrefix()) + m.Href = types.StringValue(routeAggregationRule.GetHref()) + m.Type = types.StringValue(string(routeAggregationRule.GetType())) + m.UUID = types.StringValue(routeAggregationRule.GetUuid()) + m.State = types.StringValue(string(routeAggregationRule.GetState())) routeAggregationRuleChange := routeAggregationRule.GetChange() - changeModel := ChangeModel{ - Uuid: types.StringValue(routeAggregationRuleChange.GetUuid()), + changemodel := changeModel{ + UUID: types.StringValue(routeAggregationRuleChange.GetUuid()), Type: types.StringValue(string(routeAggregationRuleChange.GetType())), Href: types.StringValue(routeAggregationRuleChange.GetHref()), } - *change = fwtypes.NewObjectValueOf[ChangeModel](ctx, &changeModel) + m.Change = fwtypes.NewObjectValueOf[changeModel](ctx, &changemodel) const TIMEFORMAT = "2008-02-02T14:02:02.000Z" routeAggregationRuleChangeLog := routeAggregationRule.GetChangeLog() - changeLogModel := ChangeLogModel{ + changelogModel := changeLogModel{ CreatedBy: types.StringValue(routeAggregationRuleChangeLog.GetCreatedBy()), CreatedByFullName: types.StringValue(routeAggregationRuleChangeLog.GetCreatedByFullName()), CreatedByEmail: types.StringValue(routeAggregationRuleChangeLog.GetCreatedByEmail()), @@ -205,6 +158,7 @@ func parseRouteAggregationRule(ctx context.Context, routeAggregationRule *fabric DeletedByEmail: types.StringValue(routeAggregationRuleChangeLog.GetDeletedByEmail()), DeletedDateTime: types.StringValue(routeAggregationRuleChangeLog.GetDeletedDateTime().Format(TIMEFORMAT)), } - *changeLog = fwtypes.NewObjectValueOf[ChangeLogModel](ctx, &changeLogModel) + m.ChangeLog = fwtypes.NewObjectValueOf[changeLogModel](ctx, &changelogModel) + return diag } diff --git a/internal/resources/fabric/route_aggregation_rule/resource.go b/internal/resources/fabric/routeaggregationrule/resource.go similarity index 84% rename from internal/resources/fabric/route_aggregation_rule/resource.go rename to internal/resources/fabric/routeaggregationrule/resource.go index 5edd9c5b7..614cee40d 100644 --- a/internal/resources/fabric/route_aggregation_rule/resource.go +++ b/internal/resources/fabric/routeaggregationrule/resource.go @@ -1,18 +1,19 @@ -package route_aggregation_rule +package routeaggregationrule import ( "context" "fmt" + "net/http" + "slices" + "strings" + "time" + "github.com/equinix/equinix-sdk-go/services/fabricv4" equinix_errors "github.com/equinix/terraform-provider-equinix/internal/errors" "github.com/equinix/terraform-provider-equinix/internal/framework" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" - "net/http" - "slices" - "strings" - "time" ) func NewResource() resource.Resource { @@ -29,7 +30,7 @@ type Resource struct { framework.BaseResource } -func (r Resource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { +func (r Resource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = resourceSchema(ctx) } @@ -38,7 +39,7 @@ func (r *Resource) Create( req resource.CreateRequest, resp *resource.CreateResponse, ) { - var plan ResourceModel + var plan resourceModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -47,14 +48,15 @@ func (r *Resource) Create( client := r.Meta.NewFabricClientForFramework(ctx, req.ProviderMeta) - routeAggregationId := plan.RouteAggregationID.ValueString() + routeAggregationID := plan.RouteAggregationID.ValueString() createRequest, diags := buildCreateRequest(ctx, plan) if diags.HasError() { + resp.Diagnostics.Append(diags...) return } - routeAggregationRule, _, err := client.RouteAggregationRulesApi.CreateRouteAggregationRule(ctx, routeAggregationId).RouteAggregationRulesBase(createRequest).Execute() + routeAggregationRule, _, err := client.RouteAggregationRulesApi.CreateRouteAggregationRule(ctx, routeAggregationID).RouteAggregationRulesBase(createRequest).Execute() if err != nil { resp.Diagnostics.AddError("Failed creating route aggregation rule", equinix_errors.FormatFabricError(err).Error()) @@ -66,18 +68,13 @@ func (r *Resource) Create( resp.Diagnostics.Append(diags...) return } - createWaiter := getCreateUpdateWaiter(ctx, client, routeAggregationId, routeAggregationRule.GetUuid(), createTimeout) + createWaiter := getCreateUpdateWaiter(ctx, client, routeAggregationID, routeAggregationRule.GetUuid(), createTimeout) routeAggregationRuleChecked, err := createWaiter.WaitForStateContext(ctx) if err != nil { resp.Diagnostics.AddError(fmt.Sprintf("Failed creating Route Aggregation %s", routeAggregationRule.GetUuid()), err.Error()) return } - resp.Diagnostics.Append(diags...) - if diags.HasError() { - return - } - resp.Diagnostics.Append(plan.parse(ctx, routeAggregationRuleChecked.(*fabricv4.RouteAggregationRulesData))...) if resp.Diagnostics.HasError() { return @@ -90,7 +87,7 @@ func (r *Resource) Read( req resource.ReadRequest, resp *resource.ReadResponse, ) { - var state ResourceModel + var state resourceModel diags := req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -101,9 +98,9 @@ func (r *Resource) Read( // Extract the ID of the resource from the state id := state.ID.ValueString() - routeAggregationId := state.RouteAggregationID.ValueString() + routeAggregationID := state.RouteAggregationID.ValueString() - routeAggregationRule, _, err := client.RouteAggregationRulesApi.GetRouteAggregationRuleByUuid(ctx, routeAggregationId, id).Execute() + routeAggregationRule, _, err := client.RouteAggregationRulesApi.GetRouteAggregationRuleByUuid(ctx, routeAggregationID, id).Execute() if err != nil { resp.Diagnostics.AddError( fmt.Sprintf("Failed retrieving Route Aggregation Rule %s", id), equinix_errors.FormatFabricError(err).Error()) @@ -127,14 +124,14 @@ func (r *Resource) Update( client := r.Meta.NewFabricClientForFramework(ctx, req.ProviderMeta) //Retrieve values from plan - var state, plan ResourceModel + var state, plan resourceModel resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) resp.Diagnostics.Append(req.State.Get(ctx, &state)...) if resp.Diagnostics.HasError() { return } id := state.ID.ValueString() - routeAggregationId := state.RouteAggregationID.ValueString() + routeAggregationID := state.RouteAggregationID.ValueString() newPrefix, oldPrefix := plan.Prefix.ValueString(), state.Prefix.ValueString() @@ -146,10 +143,10 @@ func (r *Resource) Update( updateRequest := []fabricv4.RouteAggregationRulesPatchRequestItem{{ Op: "replace", Path: "/prefix", - Value: map[string]interface{}{"": newPrefix}, + Value: newPrefix, }} - _, _, err := client.RouteAggregationRulesApi.PatchRouteAggregationRuleByUuid(ctx, routeAggregationId, id).RouteAggregationRulesPatchRequestItem(updateRequest).Execute() + _, _, err := client.RouteAggregationRulesApi.PatchRouteAggregationRuleByUuid(ctx, routeAggregationID, id).RouteAggregationRulesPatchRequestItem(updateRequest).Execute() if err != nil { resp.Diagnostics.AddError(fmt.Sprintf("Failed updating Route Aggregation %s", id), equinix_errors.FormatFabricError(err).Error()) @@ -162,7 +159,7 @@ func (r *Resource) Update( return } - updateWaiter := getCreateUpdateWaiter(ctx, client, routeAggregationId, id, updateTimeout) + updateWaiter := getCreateUpdateWaiter(ctx, client, routeAggregationID, id, updateTimeout) routeAggregationRuleChecked, err := updateWaiter.WaitForStateContext(ctx) if err != nil { resp.Diagnostics.AddError(fmt.Sprintf("Failed updating Route Aggregation Rule%s", id), err.Error()) @@ -187,15 +184,15 @@ func (r *Resource) Delete( client := r.Meta.NewFabricClientForFramework(ctx, req.ProviderMeta) //Retrieve the current state - var state ResourceModel + var state resourceModel resp.Diagnostics.Append(req.State.Get(ctx, &state)...) if resp.Diagnostics.HasError() { return } id := state.ID.ValueString() - routeAggregationId := state.RouteAggregationID.ValueString() - _, deleteResp, err := client.RouteAggregationRulesApi.DeleteRouteAggregationRuleByUuid(ctx, routeAggregationId, id).Execute() + routeAggregationID := state.RouteAggregationID.ValueString() + _, deleteResp, err := client.RouteAggregationRulesApi.DeleteRouteAggregationRuleByUuid(ctx, routeAggregationID, id).Execute() if err != nil { if deleteResp == nil || !slices.Contains([]int{http.StatusForbidden, http.StatusNotFound}, deleteResp.StatusCode) { @@ -209,7 +206,7 @@ func (r *Resource) Delete( resp.Diagnostics.Append(diags...) return } - deletewaiter := getDeleteWaiter(ctx, client, routeAggregationId, id, deleteTimeout) + deletewaiter := getDeleteWaiter(ctx, client, routeAggregationID, id, deleteTimeout) _, err = deletewaiter.WaitForStateContext(ctx) if err != nil { @@ -218,7 +215,7 @@ func (r *Resource) Delete( } } -func buildCreateRequest(ctx context.Context, plan ResourceModel) (fabricv4.RouteAggregationRulesBase, diag.Diagnostics) { +func buildCreateRequest(_ context.Context, plan resourceModel) (fabricv4.RouteAggregationRulesBase, diag.Diagnostics) { var diags diag.Diagnostics request := fabricv4.RouteAggregationRulesBase{} @@ -229,7 +226,7 @@ func buildCreateRequest(ctx context.Context, plan ResourceModel) (fabricv4.Route return request, diags } -func getCreateUpdateWaiter(ctx context.Context, client *fabricv4.APIClient, routeAggregationId string, routeAggregationRuleId string, timeout time.Duration) *retry.StateChangeConf { +func getCreateUpdateWaiter(ctx context.Context, client *fabricv4.APIClient, routeAggregationID string, routeAggregationRuleID string, timeout time.Duration) *retry.StateChangeConf { return &retry.StateChangeConf{ Pending: []string{ string(fabricv4.ROUTEAGGREGATIONRULESTATE_PROVISIONING), @@ -238,7 +235,7 @@ func getCreateUpdateWaiter(ctx context.Context, client *fabricv4.APIClient, rout string(fabricv4.ROUTEAGGREGATIONRULESTATE_PROVISIONED), }, Refresh: func() (interface{}, string, error) { - routeAggregationRule, _, err := client.RouteAggregationRulesApi.GetRouteAggregationRuleByUuid(ctx, routeAggregationId, routeAggregationRuleId).Execute() + routeAggregationRule, _, err := client.RouteAggregationRulesApi.GetRouteAggregationRuleByUuid(ctx, routeAggregationID, routeAggregationRuleID).Execute() if err != nil { return 0, "", err } @@ -250,7 +247,7 @@ func getCreateUpdateWaiter(ctx context.Context, client *fabricv4.APIClient, rout } } -func getDeleteWaiter(ctx context.Context, client *fabricv4.APIClient, routeAggregationId string, id string, timeout time.Duration) *retry.StateChangeConf { +func getDeleteWaiter(ctx context.Context, client *fabricv4.APIClient, routeAggregationID string, id string, timeout time.Duration) *retry.StateChangeConf { // deletedMarker is a terraform-provider-only value that is used by the waiter // to indicate that the resource appears to be deleted successfully based on // status code or specific error code @@ -263,7 +260,7 @@ func getDeleteWaiter(ctx context.Context, client *fabricv4.APIClient, routeAggre deletedMarker, }, Refresh: func() (interface{}, string, error) { - routeAggregationRule, resp, err := client.RouteAggregationRulesApi.GetRouteAggregationRuleByUuid(ctx, routeAggregationId, id).Execute() + routeAggregationRule, resp, err := client.RouteAggregationRulesApi.GetRouteAggregationRuleByUuid(ctx, routeAggregationID, id).Execute() if err != nil { if resp != nil { if slices.Contains([]int{http.StatusForbidden, http.StatusNotFound}, resp.StatusCode) { diff --git a/internal/resources/fabric/route_aggregation_rule/resource_schema.go b/internal/resources/fabric/routeaggregationrule/resource_schema.go similarity index 89% rename from internal/resources/fabric/route_aggregation_rule/resource_schema.go rename to internal/resources/fabric/routeaggregationrule/resource_schema.go index 897ea145d..947508a42 100644 --- a/internal/resources/fabric/route_aggregation_rule/resource_schema.go +++ b/internal/resources/fabric/routeaggregationrule/resource_schema.go @@ -1,17 +1,20 @@ -package route_aggregation_rule +package routeaggregationrule import ( "context" + "github.com/equinix/terraform-provider-equinix/internal/framework" fwtypes "github.com/equinix/terraform-provider-equinix/internal/framework/types" "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" ) func resourceSchema(ctx context.Context) schema.Schema { return schema.Schema{ + Description: `Fabric V4 API compatible resource allows creation and management of Equinix Fabric Route Aggregation + +Additional Documentation: +* API: https://developer.equinix.com/catalog/fabricv4#tag/Route-Aggregations`, Attributes: map[string]schema.Attribute{ "id": framework.IDAttributeDefaultDescription(), "timeouts": timeouts.Attributes(ctx, timeouts.Opts{ @@ -55,10 +58,7 @@ func resourceSchema(ctx context.Context) schema.Schema { "change": schema.SingleNestedAttribute{ Description: "Current state of latest route aggregation rule change", Computed: true, - PlanModifiers: []planmodifier.Object{ - objectplanmodifier.UseStateForUnknown(), - }, - CustomType: fwtypes.NewObjectTypeOf[ChangeModel](ctx), + CustomType: fwtypes.NewObjectTypeOf[changeModel](ctx), Attributes: map[string]schema.Attribute{ "uuid": schema.StringAttribute{ Description: "Equinix-assigned unique id for a change", @@ -77,10 +77,7 @@ func resourceSchema(ctx context.Context) schema.Schema { "change_log": schema.SingleNestedAttribute{ Description: "Details of the last change on the stream resource", Computed: true, - PlanModifiers: []planmodifier.Object{ - objectplanmodifier.UseStateForUnknown(), - }, - CustomType: fwtypes.NewObjectTypeOf[ChangeLogModel](ctx), + CustomType: fwtypes.NewObjectTypeOf[changeLogModel](ctx), Attributes: map[string]schema.Attribute{ "created_by": schema.StringAttribute{ Description: "User name of creator of the stream resource", diff --git a/internal/resources/fabric/route_aggregation_rule/resource_test.go b/internal/resources/fabric/routeaggregationrule/resource_test.go similarity index 69% rename from internal/resources/fabric/route_aggregation_rule/resource_test.go rename to internal/resources/fabric/routeaggregationrule/resource_test.go index 94c887bdf..63ec015a1 100644 --- a/internal/resources/fabric/route_aggregation_rule/resource_test.go +++ b/internal/resources/fabric/routeaggregationrule/resource_test.go @@ -1,4 +1,4 @@ -package route_aggregation_rule_test +package routeaggregationrule_test import ( "context" @@ -15,12 +15,12 @@ import ( "github.com/hashicorp/terraform-plugin-testing/terraform" ) -func testAccFabricRouteAggregationRuleConfig(name string) string { +func testAccFabricRouteAggregationRuleConfig(prefix string) string { return fmt.Sprintf(` resource "equinix_fabric_route_aggregation" "test" { type = "BGP_IPv4_PREFIX_AGGREGATION" - name = "%s" + name = "test-aggregation" description = "Test Route Aggregation" project = { project_id = "4f855852-eb47-4721-8e40-b386a3676abf" @@ -29,16 +29,16 @@ func testAccFabricRouteAggregationRuleConfig(name string) string { resource "equinix_fabric_route_aggregation_rule" "test" { route_aggregation_id = equinix_fabric_route_aggregation.test.id - name = "%s" + name = "RouteAggregationRulePFCR" description = "Test aggregation rule" - prefix = "192.169.0.0/24" + prefix = "%s" } - `, name) + `, prefix) } -func TestAccFabricRouteAggregationRule_PFCR(t *testing.T) { - routeAggregationRuleName := "RouteAggregationRulePFCR" - //upRouteAggregationName := "stream_up_PFCR" +func TestAccFabricRouteAggregationRule_PNFV(t *testing.T) { + routeAggregationPrefix := "192.169.0.0/24" + upRouteAggregationPrefix := "192.168.0.0/24" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acceptance.TestAccPreCheck(t); acceptance.TestAccPreCheckProviderConfigured(t) }, ExternalProviders: acceptance.TestExternalProviders, @@ -46,18 +46,30 @@ func TestAccFabricRouteAggregationRule_PFCR(t *testing.T) { CheckDestroy: CheckRouteAggregationRuleDelete, Steps: []resource.TestStep{ { - Config: testAccFabricRouteAggregationRuleConfig(routeAggregationRuleName), + Config: testAccFabricRouteAggregationRuleConfig(routeAggregationPrefix), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr( - "equinix_fabric_route_aggregation_rule.test", "name", routeAggregationRuleName), + "equinix_fabric_route_aggregation_rule.test", "name", "RouteAggregationRulePFCR"), resource.TestCheckResourceAttrSet("equinix_fabric_route_aggregation_rule.test", "uuid"), resource.TestCheckResourceAttrSet("equinix_fabric_route_aggregation_rule.test", "state"), resource.TestCheckResourceAttrSet("equinix_fabric_route_aggregation_rule.test", "href"), - resource.TestCheckResourceAttr("equinix_fabric_route_aggregation_rule.test", "name", routeAggregationRuleName), + resource.TestCheckResourceAttr("equinix_fabric_route_aggregation_rule.test", "prefix", "192.169.0.0/24"), + resource.TestCheckResourceAttr("equinix_fabric_route_aggregation_rule.test", "type", "BGP_IPv4_PREFIX_AGGREGATION_RULE"), + resource.TestCheckResourceAttr("equinix_fabric_route_aggregation_rule.test", "description", "Test aggregation rule"), + ), + }, + { + Config: testAccFabricRouteAggregationRuleConfig(upRouteAggregationPrefix), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "equinix_fabric_route_aggregation_rule.test", "name", "RouteAggregationRulePFCR"), + resource.TestCheckResourceAttrSet("equinix_fabric_route_aggregation_rule.test", "uuid"), + resource.TestCheckResourceAttrSet("equinix_fabric_route_aggregation_rule.test", "state"), + resource.TestCheckResourceAttrSet("equinix_fabric_route_aggregation_rule.test", "href"), + resource.TestCheckResourceAttr("equinix_fabric_route_aggregation_rule.test", "prefix", "192.168.0.0/24"), resource.TestCheckResourceAttr("equinix_fabric_route_aggregation_rule.test", "type", "BGP_IPv4_PREFIX_AGGREGATION_RULE"), resource.TestCheckResourceAttr("equinix_fabric_route_aggregation_rule.test", "description", "Test aggregation rule"), ), - ExpectNonEmptyPlan: false, }, }, }) @@ -74,7 +86,7 @@ func CheckRouteAggregationRuleDelete(s *terraform.State) error { } routeAggregationId := rs.Primary.Attributes["route_aggregation_id"] - _, resp, err := client.RouteAggregationRulesApi.GetRouteAggregationRuleByUuid(ctx, routeAggregationId, rs.Primary.ID).Execute() + routeAggregationRule, resp, err := client.RouteAggregationRulesApi.GetRouteAggregationRuleByUuid(ctx, routeAggregationId, rs.Primary.ID).Execute() if err != nil { // Check if the response exists and contains status 400 or 404 if resp != nil && (resp.StatusCode == 400 || resp.StatusCode == 404) { @@ -90,19 +102,16 @@ func CheckRouteAggregationRuleDelete(s *terraform.State) error { if jsonErr := json.Unmarshal(errorBody, &errorResponse); jsonErr == nil { if errorCode, exists := errorResponse["errorCode"]; exists && errorCode == "EQ-3044402" { fmt.Printf("Detected EQ-3044402 for resource %s, treating as deleted\n", rs.Primary.ID) - return nil // Successfully handled the expected deletion case + return nil } } } - - return fmt.Errorf("unexpected API error checking deletion: %v", err) - } - - if routeAggregationRule, _, err := client.RouteAggregationRulesApi.GetRouteAggregationRuleByUuid(ctx, routeAggregationId, rs.Primary.ID).Execute(); err == nil { if routeAggregationRule.GetState() == fabricv4.ROUTEAGGREGATIONRULESTATE_PROVISIONED { return fmt.Errorf("fabric stream %s still exists and is %s", rs.Primary.ID, routeAggregationRule.GetState()) } + + return fmt.Errorf("unexpected API error checking deletion: %v", err) } } return nil From 3edca712192820fc04be0709ece56d21e21356c2 Mon Sep 17 00:00:00 2001 From: srushti-patl Date: Wed, 26 Feb 2025 14:59:23 -0800 Subject: [PATCH 3/4] fix: Addressing PR comments --- .../data-source.tf | 2 +- .../data-source.tf | 2 +- .../data-source.tf | 2 +- .../data-source.tf | 2 +- .../resource.tf | 2 +- .../connectionrouteaggregation/resource.go | 2 +- .../resource_schema.go | 9 ++++++++ .../fabric/routeaggregationrule/resource.go | 16 +++++-------- .../routeaggregationrule/resource_test.go | 23 +++++-------------- 9 files changed, 27 insertions(+), 33 deletions(-) diff --git a/examples/data-sources/equinix_fabric_connection_route_aggregation/data-source.tf b/examples/data-sources/equinix_fabric_connection_route_aggregation/data-source.tf index 09deaa7b1..395d75563 100644 --- a/examples/data-sources/equinix_fabric_connection_route_aggregation/data-source.tf +++ b/examples/data-sources/equinix_fabric_connection_route_aggregation/data-source.tf @@ -17,4 +17,4 @@ output "connection_route_aggregation_type" { output "connection_route_aggregation_attachment_status" { value = data.equinix_fabric_connection_route_aggregation.attached_policy.attachment_status -} \ No newline at end of file +} diff --git a/examples/data-sources/equinix_fabric_connection_route_aggregations/data-source.tf b/examples/data-sources/equinix_fabric_connection_route_aggregations/data-source.tf index d4a1851d3..e9c6a1622 100644 --- a/examples/data-sources/equinix_fabric_connection_route_aggregations/data-source.tf +++ b/examples/data-sources/equinix_fabric_connection_route_aggregations/data-source.tf @@ -12,4 +12,4 @@ output "connection_first_route_aggregation_type" { output "connection_first_route_aggregation_attachment_status" { value = data.equinix_fabric_connection_route_aggregations.attached_policies.data.0.attachment_status -} \ No newline at end of file +} diff --git a/examples/data-sources/equinix_fabric_route_aggregation_rule/data-source.tf b/examples/data-sources/equinix_fabric_route_aggregation_rule/data-source.tf index 9a42149d6..e484f02f4 100644 --- a/examples/data-sources/equinix_fabric_route_aggregation_rule/data-source.tf +++ b/examples/data-sources/equinix_fabric_route_aggregation_rule/data-source.tf @@ -21,4 +21,4 @@ output "route_aggregation_rule_prefix" { output "route_aggregation_rule_state" { value = data.equinix_fabric_route_aggregation_rule.ra_rule.state -} \ No newline at end of file +} diff --git a/examples/data-sources/equinix_fabric_route_aggregation_rules/data-source.tf b/examples/data-sources/equinix_fabric_route_aggregation_rules/data-source.tf index 00486ddb1..1fad60094 100644 --- a/examples/data-sources/equinix_fabric_route_aggregation_rules/data-source.tf +++ b/examples/data-sources/equinix_fabric_route_aggregation_rules/data-source.tf @@ -20,4 +20,4 @@ output "route_aggregation_rule_prefix" { output "route_aggregation_rule_state" { value = data.equinix_fabric_route_aggregation_rules.ra_rules.data.0.state -} \ No newline at end of file +} diff --git a/examples/resources/equinix_fabric_connection_route_aggregation/resource.tf b/examples/resources/equinix_fabric_connection_route_aggregation/resource.tf index 11b300b86..13553fb2c 100644 --- a/examples/resources/equinix_fabric_connection_route_aggregation/resource.tf +++ b/examples/resources/equinix_fabric_connection_route_aggregation/resource.tf @@ -17,4 +17,4 @@ output "connection_route_aggregation_type" { output "connection_route_aggregation_attachment_status" { value = equinix_fabric_connection_route_aggregation.policy_attachment.attachment_status -} \ No newline at end of file +} diff --git a/internal/resources/fabric/connectionrouteaggregation/resource.go b/internal/resources/fabric/connectionrouteaggregation/resource.go index 5ea1d1a21..2e2f966ec 100644 --- a/internal/resources/fabric/connectionrouteaggregation/resource.go +++ b/internal/resources/fabric/connectionrouteaggregation/resource.go @@ -181,7 +181,7 @@ func getDeleteWaiter(ctx context.Context, client *fabricv4.APIClient, routeAggre // deletedMarker is a terraform-provider-only value that is used by the waiter // to indicate that the resource appears to be deleted successfully based on // status code or specific error code - deletedMarker := "tf-marker-for-deleted-route-aggregation-rule" + deletedMarker := "tf-marker-for-deleted-connection-route-aggregation" return &retry.StateChangeConf{ Pending: []string{ string(fabricv4.CONNECTIONROUTEAGGREGATIONDATAATTACHMENTSTATUS_DETACHING), diff --git a/internal/resources/fabric/connectionrouteaggregation/resource_schema.go b/internal/resources/fabric/connectionrouteaggregation/resource_schema.go index a4d7004c0..b7581860e 100644 --- a/internal/resources/fabric/connectionrouteaggregation/resource_schema.go +++ b/internal/resources/fabric/connectionrouteaggregation/resource_schema.go @@ -3,6 +3,9 @@ package connectionrouteaggregation import ( "context" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/equinix/terraform-provider-equinix/internal/framework" "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" "github.com/hashicorp/terraform-plugin-framework/resource/schema" @@ -21,10 +24,16 @@ func resourceSchema(ctx context.Context) schema.Schema { "route_aggregation_id": schema.StringAttribute{ Description: "UUID of the Route Aggregation to apply this Rule to", Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, }, "connection_id": schema.StringAttribute{ Description: "Equinix Assigned UUID of the Equinix Connection to attach the Route Aggregation Policy to", Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, }, "href": schema.StringAttribute{ Description: "URI to the attached Route Aggregation Policy on the Connection", diff --git a/internal/resources/fabric/routeaggregationrule/resource.go b/internal/resources/fabric/routeaggregationrule/resource.go index 614cee40d..df458833c 100644 --- a/internal/resources/fabric/routeaggregationrule/resource.go +++ b/internal/resources/fabric/routeaggregationrule/resource.go @@ -5,7 +5,6 @@ import ( "fmt" "net/http" "slices" - "strings" "time" "github.com/equinix/equinix-sdk-go/services/fabricv4" @@ -71,7 +70,7 @@ func (r *Resource) Create( createWaiter := getCreateUpdateWaiter(ctx, client, routeAggregationID, routeAggregationRule.GetUuid(), createTimeout) routeAggregationRuleChecked, err := createWaiter.WaitForStateContext(ctx) if err != nil { - resp.Diagnostics.AddError(fmt.Sprintf("Failed creating Route Aggregation %s", routeAggregationRule.GetUuid()), err.Error()) + resp.Diagnostics.AddError(fmt.Sprintf("Failed creating Route Aggregation Rule %s", routeAggregationRule.GetUuid()), err.Error()) return } @@ -263,14 +262,11 @@ func getDeleteWaiter(ctx context.Context, client *fabricv4.APIClient, routeAggre routeAggregationRule, resp, err := client.RouteAggregationRulesApi.GetRouteAggregationRuleByUuid(ctx, routeAggregationID, id).Execute() if err != nil { if resp != nil { - if slices.Contains([]int{http.StatusForbidden, http.StatusNotFound}, resp.StatusCode) { - return routeAggregationRule, deletedMarker, nil - } - apiError, ok := err.(*fabricv4.GenericOpenAPIError) - if ok { - errorBody := string(apiError.Body()) - if strings.Contains(errorBody, "EQ-3044402") { - return routeAggregationRule, deletedMarker, nil + if genericError, ok := err.(*fabricv4.GenericOpenAPIError); ok { + if fabricErrs, ok := genericError.Model().([]fabricv4.Error); ok { + if equinix_errors.HasErrorCode(fabricErrs, "EQ-3044402") { + return routeAggregationRule, deletedMarker, nil + } } } } diff --git a/internal/resources/fabric/routeaggregationrule/resource_test.go b/internal/resources/fabric/routeaggregationrule/resource_test.go index 63ec015a1..0e7aaf69c 100644 --- a/internal/resources/fabric/routeaggregationrule/resource_test.go +++ b/internal/resources/fabric/routeaggregationrule/resource_test.go @@ -2,11 +2,11 @@ package routeaggregationrule_test import ( "context" - "encoding/json" - "errors" "fmt" "testing" + equinix_errors "github.com/equinix/terraform-provider-equinix/internal/errors" + "github.com/equinix/equinix-sdk-go/services/fabricv4" "github.com/equinix/terraform-provider-equinix/internal/config" @@ -86,22 +86,11 @@ func CheckRouteAggregationRuleDelete(s *terraform.State) error { } routeAggregationId := rs.Primary.Attributes["route_aggregation_id"] - routeAggregationRule, resp, err := client.RouteAggregationRulesApi.GetRouteAggregationRuleByUuid(ctx, routeAggregationId, rs.Primary.ID).Execute() + routeAggregationRule, _, err := client.RouteAggregationRulesApi.GetRouteAggregationRuleByUuid(ctx, routeAggregationId, rs.Primary.ID).Execute() if err != nil { - // Check if the response exists and contains status 400 or 404 - if resp != nil && (resp.StatusCode == 400 || resp.StatusCode == 404) { - fmt.Printf("Resource %s not found, treating as deleted\n", rs.Primary.ID) - return nil - } - - // Handle specific API error messages - var apiErr *fabricv4.GenericOpenAPIError - if errors.As(err, &apiErr) { - errorBody := apiErr.Body() - var errorResponse map[string]interface{} - if jsonErr := json.Unmarshal(errorBody, &errorResponse); jsonErr == nil { - if errorCode, exists := errorResponse["errorCode"]; exists && errorCode == "EQ-3044402" { - fmt.Printf("Detected EQ-3044402 for resource %s, treating as deleted\n", rs.Primary.ID) + if genericError, ok := err.(*fabricv4.GenericOpenAPIError); ok { + if fabricErrs, ok := genericError.Model().([]fabricv4.Error); ok { + if equinix_errors.HasErrorCode(fabricErrs, "EQ-3044402") { return nil } } From 8ff5eb121a344320756e58e0aa106726b6b613bb Mon Sep 17 00:00:00 2001 From: srushti-patl Date: Thu, 27 Feb 2025 15:21:26 -0800 Subject: [PATCH 4/4] fix: Updating code based on PR comments --- .../fabric/routeaggregationrule/resource.go | 2 +- .../fabric/routeaggregationrule/resource_test.go | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/internal/resources/fabric/routeaggregationrule/resource.go b/internal/resources/fabric/routeaggregationrule/resource.go index df458833c..6a63aed83 100644 --- a/internal/resources/fabric/routeaggregationrule/resource.go +++ b/internal/resources/fabric/routeaggregationrule/resource.go @@ -261,7 +261,7 @@ func getDeleteWaiter(ctx context.Context, client *fabricv4.APIClient, routeAggre Refresh: func() (interface{}, string, error) { routeAggregationRule, resp, err := client.RouteAggregationRulesApi.GetRouteAggregationRuleByUuid(ctx, routeAggregationID, id).Execute() if err != nil { - if resp != nil { + if resp != nil && (resp.StatusCode == 400 || resp.StatusCode == 404) { if genericError, ok := err.(*fabricv4.GenericOpenAPIError); ok { if fabricErrs, ok := genericError.Model().([]fabricv4.Error); ok { if equinix_errors.HasErrorCode(fabricErrs, "EQ-3044402") { diff --git a/internal/resources/fabric/routeaggregationrule/resource_test.go b/internal/resources/fabric/routeaggregationrule/resource_test.go index 0e7aaf69c..f0668b3d7 100644 --- a/internal/resources/fabric/routeaggregationrule/resource_test.go +++ b/internal/resources/fabric/routeaggregationrule/resource_test.go @@ -86,12 +86,14 @@ func CheckRouteAggregationRuleDelete(s *terraform.State) error { } routeAggregationId := rs.Primary.Attributes["route_aggregation_id"] - routeAggregationRule, _, err := client.RouteAggregationRulesApi.GetRouteAggregationRuleByUuid(ctx, routeAggregationId, rs.Primary.ID).Execute() + routeAggregationRule, resp, err := client.RouteAggregationRulesApi.GetRouteAggregationRuleByUuid(ctx, routeAggregationId, rs.Primary.ID).Execute() if err != nil { - if genericError, ok := err.(*fabricv4.GenericOpenAPIError); ok { - if fabricErrs, ok := genericError.Model().([]fabricv4.Error); ok { - if equinix_errors.HasErrorCode(fabricErrs, "EQ-3044402") { - return nil + if resp != nil && (resp.StatusCode == 400 || resp.StatusCode == 404) { + if genericError, ok := err.(*fabricv4.GenericOpenAPIError); ok { + if fabricErrs, ok := genericError.Model().([]fabricv4.Error); ok { + if equinix_errors.HasErrorCode(fabricErrs, "EQ-3044402") { + return nil + } } } }