Skip to content

Commit

Permalink
feat: add support for VRF BGP dynamic neighbors
Browse files Browse the repository at this point in the history
  • Loading branch information
ctreatma committed Aug 1, 2024
1 parent f984775 commit 817a120
Show file tree
Hide file tree
Showing 7 changed files with 423 additions and 3 deletions.
24 changes: 24 additions & 0 deletions docs/data-sources/metal_vrf_bgp_dynamic_neighbor.md
Original file line number Diff line number Diff line change
@@ -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 generated by tfplugindocs -->
## 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
27 changes: 27 additions & 0 deletions docs/resources/metal_vrf_bgp_dynamic_neighbor.md
Original file line number Diff line number Diff line change
@@ -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 generated by tfplugindocs -->
## 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
8 changes: 5 additions & 3 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
}
}

Expand All @@ -132,6 +134,6 @@ func (p *FrameworkProvider) DataSources(ctx context.Context) []func() datasource
metalprojectsshkey.NewDataSource,
metalconnection.NewDataSource,
metalorganization.NewDataSource,
vlan.NewDataSource,
metalvlan.NewDataSource,
}
}
36 changes: 36 additions & 0 deletions internal/resources/metal/vrf_bgp_dynamic_neighbor/model.go
Original file line number Diff line number Diff line change
@@ -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
}
174 changes: 174 additions & 0 deletions internal/resources/metal/vrf_bgp_dynamic_neighbor/resource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
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)...)
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{}
}
Original file line number Diff line number Diff line change
@@ -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,
},
},
}
}
Loading

0 comments on commit 817a120

Please sign in to comment.