diff --git a/docs/data-sources/metal_vrf_bgp_dynamic_neighbor.md b/docs/data-sources/metal_vrf_bgp_dynamic_neighbor.md new file mode 100644 index 000000000..570bc00d3 --- /dev/null +++ b/docs/data-sources/metal_vrf_bgp_dynamic_neighbor.md @@ -0,0 +1,24 @@ +--- +subcategory: "Metal" +--- + +# equinix_metal_vrf_bgp_dynamic_neighbor (Data Source) + +This resource manages BGP dynamic neighbor ranges for an Equinix Metal VRF, but with markdown + + + + +## Schema + +### Required + +- `id` (String) The unique identifier for this the dynamic BGP neighbor + +### Read-Only + +- `asn` (Number) The ASN of the dynamic BGP neighbor +- `gateway_id` (String) The ID of the Equinix Metal VRF gateway for this dynamic BGP neighbor range +- `range` (String) Network range of the dynamic BGP neighbor in CIDR format +- `state` (String) The state of the dynamic BGP neighbor +- `tags` (List of String) Tags attached to the dynamic BGP neighbor diff --git a/docs/resources/metal_vrf_bgp_dynamic_neighbor.md b/docs/resources/metal_vrf_bgp_dynamic_neighbor.md new file mode 100644 index 000000000..80aee71fb --- /dev/null +++ b/docs/resources/metal_vrf_bgp_dynamic_neighbor.md @@ -0,0 +1,27 @@ +--- +subcategory: "Metal" +--- + +# equinix_metal_vrf_bgp_dynamic_neighbor (Resource) + +This resource manages BGP dynamic neighbor ranges for an Equinix Metal VRF, but with markdown + + + + +## Schema + +### Required + +- `asn` (Number) The ASN of the dynamic BGP neighbor +- `gateway_id` (String) The ID of the Equinix Metal VRF gateway for this dynamic BGP neighbor range +- `range` (String) Network range of the dynamic BGP neighbor in CIDR format + +### Optional + +- `tags` (List of String) Tags attached to the dynamic BGP neighbor + +### Read-Only + +- `id` (String) The unique identifier for this the dynamic BGP neighbor +- `state` (String) The state of the dynamic BGP neighbor diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 4e0d18653..d52d1a492 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -12,7 +12,8 @@ import ( metalproject "github.com/equinix/terraform-provider-equinix/internal/resources/metal/project" metalprojectsshkey "github.com/equinix/terraform-provider-equinix/internal/resources/metal/project_ssh_key" metalsshkey "github.com/equinix/terraform-provider-equinix/internal/resources/metal/ssh_key" - "github.com/equinix/terraform-provider-equinix/internal/resources/metal/vlan" + metalvlan "github.com/equinix/terraform-provider-equinix/internal/resources/metal/vlan" + metalvrfbgpdynamicneighbor "github.com/equinix/terraform-provider-equinix/internal/resources/metal/vrf_bgp_dynamic_neighbor" equinix_validation "github.com/equinix/terraform-provider-equinix/internal/validation" "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" "github.com/hashicorp/terraform-plugin-framework/datasource" @@ -121,7 +122,8 @@ func (p *FrameworkProvider) Resources(ctx context.Context) []func() resource.Res metalconnection.NewResource, metalorganization.NewResource, metalorganizationmember.NewResource, - vlan.NewResource, + metalvlan.NewResource, + metalvrfbgpdynamicneighbor.NewResource, } } @@ -132,6 +134,7 @@ func (p *FrameworkProvider) DataSources(ctx context.Context) []func() datasource metalprojectsshkey.NewDataSource, metalconnection.NewDataSource, metalorganization.NewDataSource, - vlan.NewDataSource, + metalvlan.NewDataSource, + metalvrfbgpdynamicneighbor.NewDataSource, } } diff --git a/internal/resources/metal/vrf_bgp_dynamic_neighbor/datasource.go b/internal/resources/metal/vrf_bgp_dynamic_neighbor/datasource.go new file mode 100644 index 000000000..895d0ac95 --- /dev/null +++ b/internal/resources/metal/vrf_bgp_dynamic_neighbor/datasource.go @@ -0,0 +1,70 @@ +package vrfbgpdynamicneighbor + +import ( + "context" + + "github.com/equinix/terraform-provider-equinix/internal/framework" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" +) + +type DataSource struct { + framework.BaseDataSource + framework.WithTimeouts +} + +func NewDataSource() datasource.DataSource { + return &DataSource{ + BaseDataSource: framework.NewBaseDataSource( + framework.BaseDataSourceConfig{ + Name: "equinix_metal_vrf_bgp_dynamic_neighbor", + }, + ), + } +} + +func (r *DataSource) Schema( + ctx context.Context, + req datasource.SchemaRequest, + resp *datasource.SchemaResponse, +) { + s := datasourceSchema(ctx) + if s.Blocks == nil { + s.Blocks = make(map[string]schema.Block) + } + + resp.Schema = s +} + +func (d *DataSource) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) { + client := d.Meta.NewMetalClientForFramework(ctx, request.ProviderMeta) + + var data Model + response.Diagnostics.Append(request.Config.Get(ctx, &data)...) + if response.Diagnostics.HasError() { + return + } + + neighbor, _, err := client.VRFsApi.BgpDynamicNeighborsIdGet(ctx, data.ID.ValueString()). + // `created_by` is specified as a UserLimited. To avoid an error + // due to missing UserLimited.id field, have to either exclude + // or include `created_by` + Exclude(bgpNeighborExcludes). + Include(bgpNeighborIncludes). + Execute() + + if err != nil { + response.Diagnostics.AddError( + "Error reading VRF BGP dynamic neighbor range", + "Could not read VRF BGP dynamic neighbor with ID "+data.ID.ValueString()+": "+err.Error(), + ) + } + + response.Diagnostics.Append(data.parse(ctx, neighbor)...) + if response.Diagnostics.HasError() { + return + } + + response.Diagnostics.Append(response.State.Set(ctx, &data)...) +} diff --git a/internal/resources/metal/vrf_bgp_dynamic_neighbor/datasource_schema.go b/internal/resources/metal/vrf_bgp_dynamic_neighbor/datasource_schema.go new file mode 100644 index 000000000..32c882821 --- /dev/null +++ b/internal/resources/metal/vrf_bgp_dynamic_neighbor/datasource_schema.go @@ -0,0 +1,42 @@ +package vrfbgpdynamicneighbor + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func datasourceSchema(_ context.Context) schema.Schema { + return schema.Schema{ + Description: "This resource manages BGP dynamic neighbor ranges for an Equinix Metal VRF", + MarkdownDescription: "This resource manages BGP dynamic neighbor ranges for an Equinix Metal VRF, but with markdown", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "The unique identifier for this the dynamic BGP neighbor", + Required: true, + }, + "gateway_id": schema.StringAttribute{ + Description: "The ID of the Equinix Metal VRF gateway for this dynamic BGP neighbor range", + Computed: true, + }, + "range": schema.StringAttribute{ + Description: "Network range of the dynamic BGP neighbor in CIDR format", + Computed: true, + }, + "asn": schema.Int64Attribute{ + Description: "The ASN of the dynamic BGP neighbor", + Computed: true, + }, + "state": schema.StringAttribute{ + Description: "The state of the dynamic BGP neighbor", + Computed: true, + }, + "tags": schema.ListAttribute{ + Description: "Tags attached to the dynamic BGP neighbor", + ElementType: types.StringType, + Computed: true, + }, + }, + } +} diff --git a/internal/resources/metal/vrf_bgp_dynamic_neighbor/datasource_test.go b/internal/resources/metal/vrf_bgp_dynamic_neighbor/datasource_test.go new file mode 100644 index 000000000..edee31935 --- /dev/null +++ b/internal/resources/metal/vrf_bgp_dynamic_neighbor/datasource_test.go @@ -0,0 +1,85 @@ +package vrfbgpdynamicneighbor_test + +import ( + "fmt" + "testing" + + "github.com/equinix/terraform-provider-equinix/internal/acceptance" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccDataSourceMetalVrfBgpDynamicNeighbor_basic(t *testing.T) { + rs := acctest.RandString(10) + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.TestAccPreCheckMetal(t) }, + ExternalProviders: acceptance.TestExternalProviders, + ProtoV5ProviderFactories: acceptance.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckDestroyed, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceMetalVrfBgpDynamicNeighborConfig(rs), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair( + "data.equinix_metal_vrf_bgp_dynamic_neighbor.test", "gateway_id", + "equinix_metal_gateway.test", "id"), + resource.TestCheckResourceAttr( + "data.equinix_metal_vrf_bgp_dynamic_neighbor.test", "range", + "2001:d78:0:0:4000::/66"), + resource.TestCheckResourceAttr( + "data.equinix_metal_vrf_bgp_dynamic_neighbor.test", "asn", + "56789"), + ), + }, + }, + }) +} + +func testAccDataSourceMetalVrfBgpDynamicNeighborConfig(projSuffix string) string { + return fmt.Sprintf(` +resource "equinix_metal_project" "test" { + name = "tfacc-vrf-bgp-neighbor-test-%s" +} + +resource "equinix_metal_vlan" "test" { + description = "tfacc-vlan VLAN in SV" + metro = "sv" + project_id = equinix_metal_project.test.id +} + +resource "equinix_metal_vrf" "test" { + description = "tfacc-vrf VRF in SV" + name = "tfacc-vrf-%s" + metro = "sv" + local_asn = "65000" + ip_ranges = ["2001:d78::/59"] + + project_id = equinix_metal_project.test.id +} + +resource "equinix_metal_reserved_ip_block" "test" { + project_id = equinix_metal_project.test.id + type = "vrf" + vrf_id = equinix_metal_vrf.test.id + network = "2001:d78::" + metro = "sv" + cidr = 64 +} + +resource "equinix_metal_gateway" "test" { + project_id = equinix_metal_project.test.id + vlan_id = equinix_metal_vlan.test.id + ip_reservation_id = equinix_metal_reserved_ip_block.test.id +} + +resource "equinix_metal_vrf_bgp_dynamic_neighbor" "test" { + gateway_id = equinix_metal_gateway.test.id + range = "2001:d78:0:0:4000::/66" + asn = "56789" +} + +data "equinix_metal_vrf_bgp_dynamic_neighbor" "test" { + id = equinix_metal_vrf_bgp_dynamic_neighbor.test.id +} +`, projSuffix, projSuffix) +} diff --git a/internal/resources/metal/vrf_bgp_dynamic_neighbor/model.go b/internal/resources/metal/vrf_bgp_dynamic_neighbor/model.go new file mode 100644 index 000000000..5d3589c03 --- /dev/null +++ b/internal/resources/metal/vrf_bgp_dynamic_neighbor/model.go @@ -0,0 +1,36 @@ +package vrfbgpdynamicneighbor + +import ( + "context" + + "github.com/equinix/equinix-sdk-go/services/metalv1" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type Model struct { + ID types.String `tfsdk:"id"` + GatewayID types.String `tfsdk:"gateway_id"` + Range types.String `tfsdk:"range"` + ASN types.Int64 `tfsdk:"asn"` + State types.String `tfsdk:"state"` + Tags types.List `tfsdk:"tags"` // List of strings +} + +func (m *Model) parse(ctx context.Context, neighbor *metalv1.BgpDynamicNeighbor) (d diag.Diagnostics) { + m.ID = types.StringValue(neighbor.GetId()) + + m.GatewayID = types.StringValue(neighbor.MetalGateway.GetId()) + m.Range = types.StringValue(neighbor.GetBgpNeighborRange()) + m.ASN = types.Int64Value(neighbor.GetBgpNeighborAsn()) + m.State = types.StringValue(string(neighbor.GetState())) + + tags, diags := types.ListValueFrom(ctx, types.StringType, neighbor.GetTags()) + if diags.HasError() { + return diags + } + + m.Tags = tags + + return nil +} diff --git a/internal/resources/metal/vrf_bgp_dynamic_neighbor/resource.go b/internal/resources/metal/vrf_bgp_dynamic_neighbor/resource.go new file mode 100644 index 000000000..f1bafd6e6 --- /dev/null +++ b/internal/resources/metal/vrf_bgp_dynamic_neighbor/resource.go @@ -0,0 +1,177 @@ +package vrfbgpdynamicneighbor + +import ( + "context" + + "github.com/equinix/equinix-sdk-go/services/metalv1" + "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-framework/resource/schema" +) + +var ( + bgpNeighborIncludes = []string{"metal_gateway"} + // `created_by` is specified as a UserLimited. To avoid an error + // due to missing UserLimited.id field, have to either exclude + // or include `created_by` + bgpNeighborExcludes = []string{"created_by", "ip_reservation"} +) + +type Resource struct { + framework.BaseResource + framework.WithTimeouts +} + +func NewResource() resource.Resource { + r := Resource{ + BaseResource: framework.NewBaseResource( + framework.BaseResourceConfig{ + Name: "equinix_metal_vrf_bgp_dynamic_neighbor", + }, + ), + } + + return &r +} + +func (r *Resource) Schema( + ctx context.Context, + req resource.SchemaRequest, + resp *resource.SchemaResponse, +) { + s := resourceSchema(ctx) + if s.Blocks == nil { + s.Blocks = make(map[string]schema.Block) + } + + resp.Schema = s +} + +func (r *Resource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + client := r.Meta.NewMetalClientForFramework(ctx, request.ProviderMeta) + + var plan Model + response.Diagnostics.Append(request.Config.Get(ctx, &plan)...) + if response.Diagnostics.HasError() { + return + } + + createRequest := metalv1.BgpDynamicNeighborCreateInput{ + BgpNeighborRange: plan.Range.ValueString(), + BgpNeighborAsn: plan.ASN.ValueInt64(), + } + + response.Diagnostics.Append(getPlanTags(ctx, plan, &createRequest.Tags)...) + + // Parse API response into the Terraform state + //response.Diagnostics.Append(data.parse(vlan)...) + if response.Diagnostics.HasError() { + return + } + + neighbor, _, err := client.VRFsApi.CreateBgpDynamicNeighbor(ctx, plan.GatewayID.ValueString()). + BgpDynamicNeighborCreateInput(createRequest). + Exclude(bgpNeighborExcludes). + Include(bgpNeighborIncludes). + Execute() + + if err != nil { + response.Diagnostics.AddError( + "Error creating VRF BGP dynamic neighbor range", + "Could not create VRF BGP dynamic neighbor range: "+err.Error(), + ) + } + + // Parse API response into the Terraform state + response.Diagnostics.Append(plan.parse(ctx, neighbor)...) + if response.Diagnostics.HasError() { + return + } + + // Set state to fully populated data + response.Diagnostics.Append(response.State.Set(ctx, &plan)...) +} + +func (r *Resource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + client := r.Meta.NewMetalClientForFramework(ctx, request.ProviderMeta) + + var data Model + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + if response.Diagnostics.HasError() { + return + } + + neighbor, _, err := client.VRFsApi.BgpDynamicNeighborsIdGet(ctx, data.ID.ValueString()). + // `created_by` is specified as a UserLimited. To avoid an error + // due to missing UserLimited.id field, have to either exclude + // or include `created_by` + Exclude(bgpNeighborExcludes). + Include(bgpNeighborIncludes). + Execute() + + if err != nil { + response.Diagnostics.AddError( + "Error reading VRF BGP dynamic neighbor range", + "Could not read VRF BGP dynamic neighbor with ID "+data.ID.ValueString()+": "+err.Error(), + ) + } + + response.Diagnostics.Append(data.parse(ctx, neighbor)...) + if response.Diagnostics.HasError() { + return + } + + response.Diagnostics.Append(response.State.Set(ctx, &data)...) +} + +func (r *Resource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // TODO: it should be possible to update tags but the API spec doesn't + // mention support for that. In the meantime should changes to tags force + // recreating the resource? + var data Model + if diag := req.Plan.Get(ctx, &data); diag.HasError() { + resp.Diagnostics.Append(diag...) + return + } + + if diag := resp.State.Set(ctx, &data); diag.HasError() { + resp.Diagnostics.Append(diag...) + return + } +} + +func (r *Resource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { + client := r.Meta.NewMetalClientForFramework(ctx, request.ProviderMeta) + + var data Model + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + if response.Diagnostics.HasError() { + return + } + + // TODO: should we do something with the neighbor object returned here? + // For example: do we need to poll the API until neighbor.GetState() has + // as particular value? + _, _, err := client.VRFsApi.DeleteBgpDynamicNeighborById(ctx, data.ID.ValueString()). + // `created_by` is specified as a UserLimited. To avoid an error + // due to missing UserLimited.id field, have to either exclude + // or include `created_by` + Exclude(bgpNeighborExcludes). + Execute() + + if err != nil { + response.Diagnostics.AddError( + "Error deleting VRF BGP dynamic neighbor range", + "Could not delete VRF BGP dynamic neighbor with ID "+data.ID.ValueString()+": "+err.Error(), + ) + } +} + +func getPlanTags(ctx context.Context, plan Model, tags *[]string) diag.Diagnostics { + if len(plan.Tags.Elements()) != 0 { + return plan.Tags.ElementsAs(context.Background(), tags, false) + } + return diag.Diagnostics{} +} diff --git a/internal/resources/metal/vrf_bgp_dynamic_neighbor/resource_schema.go b/internal/resources/metal/vrf_bgp_dynamic_neighbor/resource_schema.go new file mode 100644 index 000000000..2438c8e03 --- /dev/null +++ b/internal/resources/metal/vrf_bgp_dynamic_neighbor/resource_schema.go @@ -0,0 +1,58 @@ +package vrfbgpdynamicneighbor + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func resourceSchema(_ context.Context) schema.Schema { + return schema.Schema{ + Description: "This resource manages BGP dynamic neighbor ranges for an Equinix Metal VRF", + MarkdownDescription: "This resource manages BGP dynamic neighbor ranges for an Equinix Metal VRF, but with markdown", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "The unique identifier for this the dynamic BGP neighbor", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "gateway_id": schema.StringAttribute{ + Description: "The ID of the Equinix Metal VRF gateway for this dynamic BGP neighbor range", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "range": schema.StringAttribute{ + Description: "Network range of the dynamic BGP neighbor in CIDR format", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "asn": schema.Int64Attribute{ + Description: "The ASN of the dynamic BGP neighbor", + Required: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.RequiresReplace(), + }, + }, + "state": schema.StringAttribute{ + Description: "The state of the dynamic BGP neighbor", + Computed: true, + }, + "tags": schema.ListAttribute{ + Description: "Tags attached to the dynamic BGP neighbor", + ElementType: types.StringType, + Optional: true, + Computed: true, + }, + }, + } +} diff --git a/internal/resources/metal/vrf_bgp_dynamic_neighbor/resource_test.go b/internal/resources/metal/vrf_bgp_dynamic_neighbor/resource_test.go new file mode 100644 index 000000000..ed4314356 --- /dev/null +++ b/internal/resources/metal/vrf_bgp_dynamic_neighbor/resource_test.go @@ -0,0 +1,99 @@ +package vrfbgpdynamicneighbor_test + +import ( + "context" + "fmt" + "testing" + + "github.com/equinix/terraform-provider-equinix/internal/acceptance" + "github.com/equinix/terraform-provider-equinix/internal/config" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +func TestAccMetalVrfBgpDynamicNeighbor_basic(t *testing.T) { + rs := acctest.RandString(10) + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.TestAccPreCheckMetal(t) }, + ExternalProviders: acceptance.TestExternalProviders, + ProtoV5ProviderFactories: acceptance.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckDestroyed, + Steps: []resource.TestStep{ + { + Config: testAccMetalVrfBgpDynamicNeighborConfig(rs), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair( + "equinix_metal_vrf_bgp_dynamic_neighbor.test", "gateway_id", + "equinix_metal_gateway.test", "id"), + resource.TestCheckResourceAttr( + "equinix_metal_vrf_bgp_dynamic_neighbor.test", "range", + "2001:d78:0:0:4000::/66"), + resource.TestCheckResourceAttr( + "equinix_metal_vrf_bgp_dynamic_neighbor.test", "asn", + "56789"), + ), + }, + }, + }) +} + +func testAccMetalVrfBgpDynamicNeighborConfig(projSuffix string) string { + return fmt.Sprintf(` +resource "equinix_metal_project" "test" { + name = "tfacc-vrf-bgp-neighbor-test-%s" +} + +resource "equinix_metal_vlan" "test" { + description = "tfacc-vlan VLAN in SV" + metro = "sv" + project_id = equinix_metal_project.test.id +} + +resource "equinix_metal_vrf" "test" { + description = "tfacc-vrf VRF in SV" + name = "tfacc-vrf-%s" + metro = "sv" + local_asn = "65000" + ip_ranges = ["2001:d78::/59"] + + project_id = equinix_metal_project.test.id +} + +resource "equinix_metal_reserved_ip_block" "test" { + project_id = equinix_metal_project.test.id + type = "vrf" + vrf_id = equinix_metal_vrf.test.id + network = "2001:d78::" + metro = "sv" + cidr = 64 +} + +resource "equinix_metal_gateway" "test" { + project_id = equinix_metal_project.test.id + vlan_id = equinix_metal_vlan.test.id + ip_reservation_id = equinix_metal_reserved_ip_block.test.id +} + +resource "equinix_metal_vrf_bgp_dynamic_neighbor" "test" { + gateway_id = equinix_metal_gateway.test.id + range = "2001:d78:0:0:4000::/66" + asn = "56789" +} +`, projSuffix, projSuffix) +} + +func testAccCheckDestroyed(s *terraform.State) error { + client := acceptance.TestAccProvider.Meta().(*config.Config).NewMetalClientForTesting() + + for _, rs := range s.RootModule().Resources { + if rs.Type != "equinix_metal_vrf_bgp_dynamic_gateway" { + continue + } + if _, _, err := client.VRFsApi.BgpDynamicNeighborsIdGet(context.Background(), rs.Primary.ID).Execute(); err == nil { + return fmt.Errorf("Metal VRF BGP dynamic neighbor still exists") + } + } + + return nil +}