From e1fd9da1ef5dbafa8ccdf7abfe4da2e16a66b18d Mon Sep 17 00:00:00 2001 From: Luke Browning Date: Sun, 15 Oct 2023 02:36:56 -0700 Subject: [PATCH 1/7] Update opnsense-go to v0.7.0 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6d09f10..5c75ce7 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module terraform-provider-opnsense go 1.20 require ( - github.com/browningluke/opnsense-go v0.6.0 + github.com/browningluke/opnsense-go v0.7.0 github.com/hashicorp/terraform-plugin-docs v0.14.1 github.com/hashicorp/terraform-plugin-framework v1.2.0 github.com/hashicorp/terraform-plugin-framework-validators v0.10.0 diff --git a/go.sum b/go.sum index a54afd4..945b4c4 100644 --- a/go.sum +++ b/go.sum @@ -21,8 +21,8 @@ github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgI github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/browningluke/opnsense-go v0.6.0 h1:ikAWboweTg7+m6kAW8lzn0dAnD7qtlfD9Q89r0kY4l0= -github.com/browningluke/opnsense-go v0.6.0/go.mod h1:u8di5DaP7igyIBl02jjrAbXYKRtEelnRi3Tqp5aj7g0= +github.com/browningluke/opnsense-go v0.7.0 h1:y6nhqpYnqTvlKx8bZk66n3Bx0mnlR5BfJ8K3sSMW+Jc= +github.com/browningluke/opnsense-go v0.7.0/go.mod h1:u8di5DaP7igyIBl02jjrAbXYKRtEelnRi3Tqp5aj7g0= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= From 1df4cd4a276526edc8fd26b9f835fa26a27ef6fe Mon Sep 17 00:00:00 2001 From: Luke Browning Date: Sun, 15 Oct 2023 02:37:46 -0700 Subject: [PATCH 2/7] Add BGP neighbor resource & data source --- internal/provider/provider.go | 4 + .../quagga_bgp_neighbor_data_source.go | 80 ++++ .../service/quagga_bgp_neighbor_resource.go | 182 ++++++++ .../service/quagga_bgp_neighbor_schema.go | 399 ++++++++++++++++++ 4 files changed, 665 insertions(+) create mode 100644 internal/service/quagga_bgp_neighbor_data_source.go create mode 100644 internal/service/quagga_bgp_neighbor_resource.go create mode 100644 internal/service/quagga_bgp_neighbor_schema.go diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 3e8f223..ae83266 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -122,6 +122,8 @@ func (p *OPNsenseProvider) Resources(ctx context.Context) []func() resource.Reso // Wireguard service.NewWireguardServerResource, service.NewWireguardClientResource, + // Quagga + service.NewQuaggaBGPNeighborResource, // Firewall service.NewFirewallFilterResource, service.NewFirewallNATResource, @@ -144,6 +146,8 @@ func (p *OPNsenseProvider) DataSources(ctx context.Context) []func() datasource. // Wireguard service.NewWireguardServerDataSource, service.NewWireguardClientDataSource, + // Quagga + service.NewQuaggaBGPNeighborDataSource, // Firewall service.NewFirewallFilterDataSource, service.NewFirewallNATDataSource, diff --git a/internal/service/quagga_bgp_neighbor_data_source.go b/internal/service/quagga_bgp_neighbor_data_source.go new file mode 100644 index 0000000..a741e40 --- /dev/null +++ b/internal/service/quagga_bgp_neighbor_data_source.go @@ -0,0 +1,80 @@ +package service + +import ( + "context" + "fmt" + "github.com/browningluke/opnsense-go/pkg/api" + "github.com/browningluke/opnsense-go/pkg/opnsense" + "github.com/hashicorp/terraform-plugin-framework/datasource" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ datasource.DataSource = &QuaggaBGPNeighborDataSource{} + +func NewQuaggaBGPNeighborDataSource() datasource.DataSource { + return &QuaggaBGPNeighborDataSource{} +} + +// QuaggaBGPNeighborDataSource defines the data source implementation. +type QuaggaBGPNeighborDataSource struct { + client opnsense.Client +} + +func (d *QuaggaBGPNeighborDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_quagga_bgp_neighbor" +} + +func (d *QuaggaBGPNeighborDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = QuaggaBGPNeighborDataSourceSchema() +} + +func (d *QuaggaBGPNeighborDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + apiClient, ok := req.ProviderData.(*api.Client) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *opnsense.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + + d.client = opnsense.NewClient(apiClient) +} + +func (d *QuaggaBGPNeighborDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data *QuaggaBGPNeighborResourceModel + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Get resource from OPNsense API + resource, err := d.client.Quagga().GetBGPNeighbor(ctx, data.Id.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to read neighbor, got error: %s", err)) + return + } + + // Convert OPNsense struct to TF schema + resourceModel, err := convertQuaggaBGPNeighborStructToSchema(resource) + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to read neighbor, got error: %s", err)) + return + } + + // ID cannot be added by convert... func, have to add here + resourceModel.Id = data.Id + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &resourceModel)...) +} diff --git a/internal/service/quagga_bgp_neighbor_resource.go b/internal/service/quagga_bgp_neighbor_resource.go new file mode 100644 index 0000000..902fc7a --- /dev/null +++ b/internal/service/quagga_bgp_neighbor_resource.go @@ -0,0 +1,182 @@ +package service + +import ( + "context" + "errors" + "fmt" + "github.com/browningluke/opnsense-go/pkg/api" + "github.com/browningluke/opnsense-go/pkg/errs" + "github.com/browningluke/opnsense-go/pkg/opnsense" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &QuaggaBGPNeighborResource{} +var _ resource.ResourceWithImportState = &QuaggaBGPNeighborResource{} + +func NewQuaggaBGPNeighborResource() resource.Resource { + return &QuaggaBGPNeighborResource{} +} + +// QuaggaBGPNeighborResource defines the resource implementation. +type QuaggaBGPNeighborResource struct { + client opnsense.Client +} + +func (r *QuaggaBGPNeighborResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_quagga_bgp_neighbor" +} + +func (r *QuaggaBGPNeighborResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = quaggaBGPNeighborResourceSchema() +} + +func (r *QuaggaBGPNeighborResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + apiClient, ok := req.ProviderData.(*api.Client) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *opnsense.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + + r.client = opnsense.NewClient(apiClient) +} + +func (r *QuaggaBGPNeighborResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data *QuaggaBGPNeighborResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Convert TF schema OPNsense struct + bgpNeighbor, err := convertQuaggaBGPNeighborSchemaToStruct(data) + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to parse bgp neighbor, got error: %s", err)) + return + } + + // Add bgp neighbor to unbound + id, err := r.client.Quagga().AddBGPNeighbor(ctx, bgpNeighbor) + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to create bgp neighbor, got error: %s", err)) + return + } + + // Tag new resource with ID from OPNsense + data.Id = types.StringValue(id) + + // Write logs using the tflog package + tflog.Trace(ctx, "created a resource") + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *QuaggaBGPNeighborResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data *QuaggaBGPNeighborResourceModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Get bgp neighbor from OPNsense unbound API + bgpNeighbor, err := r.client.Quagga().GetBGPNeighbor(ctx, data.Id.ValueString()) + if err != nil { + var notFoundError *errs.NotFoundError + if errors.As(err, ¬FoundError) { + tflog.Warn(ctx, fmt.Sprintf("bgp neighbor not present in remote, removing from state")) + resp.State.RemoveResource(ctx) + return + } + + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to read bgp neighbor, got error: %s", err)) + return + } + + // Convert OPNsense struct to TF schema + bgpNeighborModel, err := convertQuaggaBGPNeighborStructToSchema(bgpNeighbor) + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to read bgp neighbor, got error: %s", err)) + return + } + + // ID cannot be added by convert... func, have to add here + bgpNeighborModel.Id = data.Id + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &bgpNeighborModel)...) +} + +func (r *QuaggaBGPNeighborResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data *QuaggaBGPNeighborResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Convert TF schema OPNsense struct + bgpNeighbor, err := convertQuaggaBGPNeighborSchemaToStruct(data) + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to parse bgp neighbor, got error: %s", err)) + return + } + + // Update bgp neighbor in unbound + err = r.client.Quagga().UpdateBGPNeighbor(ctx, data.Id.ValueString(), bgpNeighbor) + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to create bgp neighbor, got error: %s", err)) + return + } + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *QuaggaBGPNeighborResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data *QuaggaBGPNeighborResourceModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + err := r.client.Quagga().DeleteBGPNeighbor(ctx, data.Id.ValueString()) + + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to delete bgp neighbor, got error: %s", err)) + return + } +} + +func (r *QuaggaBGPNeighborResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} diff --git a/internal/service/quagga_bgp_neighbor_schema.go b/internal/service/quagga_bgp_neighbor_schema.go new file mode 100644 index 0000000..166aff3 --- /dev/null +++ b/internal/service/quagga_bgp_neighbor_schema.go @@ -0,0 +1,399 @@ +package service + +import ( + "github.com/browningluke/opnsense-go/pkg/api" + "github.com/browningluke/opnsense-go/pkg/quagga" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + dschema "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "terraform-provider-opnsense/internal/tools" +) + +// QuaggaBGPNeighborResourceModel describes the resource data model. +type QuaggaBGPNeighborResourceModel struct { + Enabled types.Bool `tfsdk:"enabled"` + Description types.String `tfsdk:"description"` + PeerIP types.String `tfsdk:"peer_ip"` + RemoteAS types.Int64 `tfsdk:"remote_as"` + Password types.String `tfsdk:"md5_password"` + Weight types.Int64 `tfsdk:"weight"` + LocalIP types.String `tfsdk:"local_ip"` + UpdateSource types.String `tfsdk:"update_source"` + LinkLocalInterface types.String `tfsdk:"link_local_interface"` + NextHopSelf types.Bool `tfsdk:"next_hop_self"` + NextHopSelfAll types.Bool `tfsdk:"next_hop_self_all"` + MultiHop types.Bool `tfsdk:"multi_hop"` + MultiProtocol types.Bool `tfsdk:"multi_protocol"` + RRClient types.Bool `tfsdk:"rr_client"` + BFD types.Bool `tfsdk:"bfd"` + KeepAlive types.Int64 `tfsdk:"keep_alive"` + HoldDown types.Int64 `tfsdk:"hold_down"` + ConnectTimer types.Int64 `tfsdk:"connect_timer"` + DefaultRoute types.Bool `tfsdk:"default_route"` + ASOverride types.Bool `tfsdk:"as_override"` + DisableConnectedCheck types.Bool `tfsdk:"disable_connected_check"` + AttributeUnchanged types.String `tfsdk:"attribute_unchanged"` + PrefixListIn types.String `tfsdk:"prefix_list_in"` + PrefixListOut types.String `tfsdk:"prefix_list_out"` + RouteMapIn types.String `tfsdk:"route_map_in"` + RouteMapOut types.String `tfsdk:"route_map_out"` + + Id types.String `tfsdk:"id"` +} + +func quaggaBGPNeighborResourceSchema() schema.Schema { + return schema.Schema{ + MarkdownDescription: "Configure neighbors for BGP.", + + Attributes: map[string]schema.Attribute{ + "enabled": schema.BoolAttribute{ + MarkdownDescription: "Enable this neighbor. Defaults to `true`.", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(true), + }, + "description": schema.StringAttribute{ + MarkdownDescription: "An optional description for this neighbor. Defaults to `\"\"`.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + }, + "peer_ip": schema.StringAttribute{ + MarkdownDescription: "The IP of your neighbor.", + Required: true, + }, + "remote_as": schema.Int64Attribute{ + MarkdownDescription: "The neighbor AS.", + Required: true, + }, + "md5_password": schema.StringAttribute{ + MarkdownDescription: "The password for BGP authentication. Defaults to `\"\"`.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + }, + "weight": schema.Int64Attribute{ + MarkdownDescription: "Specify a default weight value for the neighbor’s routes. Defaults to `-1`.", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(-1), + }, + "local_ip": schema.StringAttribute{ + MarkdownDescription: "The local IP connecting to the neighbor. This is only required for BGP authentication. Defaults to `\"\"`.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + }, + "update_source": schema.StringAttribute{ + MarkdownDescription: "Physical name of the IPv4 interface facing the peer. Must be a valid OPNsense interface in lowercase (e.g. `wan`). Please refer to the FRR documentation for more information. Defaults to `\"\"`.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + }, + "link_local_interface": schema.StringAttribute{ + MarkdownDescription: "Interface to use for IPv6 link-local neighbours. Must be a valid OPNsense interface in lowercase (e.g. `wan`). Please refer to the FRR documentation for more information. Defaults to `\"\"`.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + }, + "next_hop_self": schema.BoolAttribute{ + MarkdownDescription: "Enable the next-hop-self command. Defaults to `false`.", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "next_hop_self_all": schema.BoolAttribute{ + MarkdownDescription: "Add the parameter \"all\" after next-hop-self command. Defaults to `false`.", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "multi_hop": schema.BoolAttribute{ + MarkdownDescription: "Enable multi-hop. Specifying ebgp-multihop allows sessions with eBGP neighbors to establish when they are multiple hops away. When the neighbor is not directly connected and this knob is not enabled, the session will not establish. Defaults to `false`.", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "multi_protocol": schema.BoolAttribute{ + MarkdownDescription: "Mark this neighbor as multiprotocol capable per RFC 2283. Defaults to `false`.", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "rr_client": schema.BoolAttribute{ + MarkdownDescription: "Enable route reflector client. Defaults to `false`.", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "bfd": schema.BoolAttribute{ + MarkdownDescription: "Enable BFD support for this neighbor. Defaults to `false`.", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "keep_alive": schema.Int64Attribute{ + MarkdownDescription: "Enable Keepalive timer to check if the neighbor is still up. Defaults to `60`.", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(60), + }, + "hold_down": schema.Int64Attribute{ + MarkdownDescription: "The time in seconds when a neighbor is considered dead. This is usually 3 times the keepalive timer. Defaults to `180`.", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(180), + }, + "connect_timer": schema.Int64Attribute{ + MarkdownDescription: "The time in seconds how fast a neighbor tries to reconnect. Defaults to `-1`.", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(-1), + }, + "default_route": schema.BoolAttribute{ + MarkdownDescription: "Enable to send Defaultroute. Defaults to `false`.", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "as_override": schema.BoolAttribute{ + MarkdownDescription: "Override AS number of the originating router with the local AS number. This command is only allowed for eBGP peers. Defaults to `false`.", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "disable_connected_check": schema.BoolAttribute{ + MarkdownDescription: "Enable to allow peerings between directly connected eBGP peers using loopback addresses. Defaults to `false`.", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "attribute_unchanged": schema.StringAttribute{ + MarkdownDescription: "Specify attribute to be left unchanged when sending advertisements to a peer. Read more at FRR documentation. Defaults to `\"\"`.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + Validators: []validator.String{ + stringvalidator.OneOf("", "as-path", "next-hop", "med"), + }, + }, + "prefix_list_in": schema.StringAttribute{ + MarkdownDescription: "The prefix list ID for inbound direction. Defaults to `\"\"`.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + }, + "prefix_list_out": schema.StringAttribute{ + MarkdownDescription: "The prefix list ID for outbound direction. Defaults to `\"\"`.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + }, + "route_map_in": schema.StringAttribute{ + MarkdownDescription: "The route map ID for inbound direction. Defaults to `\"\"`.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + }, + "route_map_out": schema.StringAttribute{ + MarkdownDescription: "The route map ID for outbound direction. Defaults to `\"\"`.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + }, + "id": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "UUID of the neighbor.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + } +} + +func QuaggaBGPNeighborDataSourceSchema() dschema.Schema { + return dschema.Schema{ + MarkdownDescription: "Configure neighbors for BGP.", + + Attributes: map[string]dschema.Attribute{ + "id": dschema.StringAttribute{ + MarkdownDescription: "UUID of the resource.", + Required: true, + }, + "enabled": dschema.BoolAttribute{ + MarkdownDescription: "Enable this neighbor.", + Computed: true, + }, + "description": dschema.StringAttribute{ + MarkdownDescription: "An optional description for this neighbor.", + Computed: true, + }, + "peer_ip": dschema.StringAttribute{ + MarkdownDescription: "The IP of your neighbor.", + Computed: true, + }, + "remote_as": dschema.Int64Attribute{ + MarkdownDescription: "The neighbor AS.", + Computed: true, + }, + "md5_password": dschema.StringAttribute{ + MarkdownDescription: "The password for BGP authentication.", + Computed: true, + }, + "weight": dschema.Int64Attribute{ + MarkdownDescription: "Specify a default weight value for the neighbor’s routes.", + Computed: true, + }, + "local_ip": dschema.StringAttribute{ + MarkdownDescription: "The local IP connecting to the neighbor. This is only required for BGP authentication.", + Computed: true, + }, + "update_source": dschema.StringAttribute{ + MarkdownDescription: "Physical name of the IPv4 interface facing the peer. Must be a valid OPNsense interface in lowercase (e.g. `wan`). Please refer to the FRR documentation for more information.", + Computed: true, + }, + "link_local_interface": dschema.StringAttribute{ + MarkdownDescription: "Interface to use for IPv6 link-local neighbours. Must be a valid OPNsense interface in lowercase (e.g. `wan`). Please refer to the FRR documentation for more information.", + Computed: true, + }, + "next_hop_self": dschema.BoolAttribute{ + MarkdownDescription: "Enable the next-hop-self command.", + Computed: true, + }, + "next_hop_self_all": dschema.BoolAttribute{ + MarkdownDescription: "Add the parameter \"all\" after next-hop-self command.", + Computed: true, + }, + "multi_hop": dschema.BoolAttribute{ + MarkdownDescription: "Enable multi-hop. Specifying ebgp-multihop allows sessions with eBGP neighbors to establish when they are multiple hops away. When the neighbor is not directly connected and this knob is not enabled, the session will not establish.", + Computed: true, + }, + "multi_protocol": dschema.BoolAttribute{ + MarkdownDescription: "Mark this neighbor as multiprotocol capable per RFC 2283.", + Computed: true, + }, + "rr_client": dschema.BoolAttribute{ + MarkdownDescription: "Enable route reflector client.", + Computed: true, + }, + "bfd": dschema.BoolAttribute{ + MarkdownDescription: "Enable BFD support for this neighbor.", + Computed: true, + }, + "keep_alive": dschema.Int64Attribute{ + MarkdownDescription: "Enable Keepalive timer to check if the neighbor is still up.", + Computed: true, + }, + "hold_down": dschema.Int64Attribute{ + MarkdownDescription: "The time in seconds when a neighbor is considered dead. This is usually 3 times the keepalive timer.", + Computed: true, + }, + "connect_timer": dschema.Int64Attribute{ + MarkdownDescription: "The time in seconds how fast a neighbor tries to reconnect.", + Computed: true, + }, + "default_route": dschema.BoolAttribute{ + MarkdownDescription: "Enable to send Defaultroute.", + Computed: true, + }, + "as_override": dschema.BoolAttribute{ + MarkdownDescription: "Override AS number of the originating router with the local AS number. This command is only allowed for eBGP peers.", + Computed: true, + }, + "disable_connected_check": dschema.BoolAttribute{ + MarkdownDescription: "Enable to allow peerings between directly connected eBGP peers using loopback addresses.", + Computed: true, + }, + "attribute_unchanged": dschema.StringAttribute{ + MarkdownDescription: "Specify attribute to be left unchanged when sending advertisements to a peer. Read more at FRR documentation.", + Computed: true, + }, + "prefix_list_in": dschema.StringAttribute{ + MarkdownDescription: "The prefix list ID for inbound direction.", + Computed: true, + }, + "prefix_list_out": dschema.StringAttribute{ + MarkdownDescription: "The prefix list ID for outbound direction.", + Computed: true, + }, + "route_map_in": dschema.StringAttribute{ + MarkdownDescription: "The route map ID for inbound direction.", + Computed: true, + }, + "route_map_out": dschema.StringAttribute{ + MarkdownDescription: "The route map ID for outbound direction.", + Computed: true, + }, + }, + } +} + +func convertQuaggaBGPNeighborSchemaToStruct(d *QuaggaBGPNeighborResourceModel) (*quagga.BGPNeighbor, error) { + return &quagga.BGPNeighbor{ + Enabled: tools.BoolToString(d.Enabled.ValueBool()), + Description: d.Description.ValueString(), + PeerIP: d.PeerIP.ValueString(), + RemoteAS: tools.Int64ToString(d.RemoteAS.ValueInt64()), + Password: d.Password.ValueString(), + Weight: tools.Int64ToStringNegative(d.Weight.ValueInt64()), + LocalIP: d.LocalIP.ValueString(), + UpdateSource: api.SelectedMap(d.UpdateSource.ValueString()), + LinkLocalInterface: api.SelectedMap(d.LinkLocalInterface.ValueString()), + NextHopSelf: tools.BoolToString(d.NextHopSelf.ValueBool()), + NextHopSelfAll: tools.BoolToString(d.NextHopSelfAll.ValueBool()), + MultiHop: tools.BoolToString(d.MultiHop.ValueBool()), + MultiProtocol: tools.BoolToString(d.MultiProtocol.ValueBool()), + RRClient: tools.BoolToString(d.RRClient.ValueBool()), + BFD: tools.BoolToString(d.BFD.ValueBool()), + KeepAlive: tools.Int64ToString(d.KeepAlive.ValueInt64()), + HoldDown: tools.Int64ToString(d.HoldDown.ValueInt64()), + ConnectTimer: tools.Int64ToStringNegative(d.ConnectTimer.ValueInt64()), + DefaultRoute: tools.BoolToString(d.DefaultRoute.ValueBool()), + ASOverride: tools.BoolToString(d.ASOverride.ValueBool()), + DisableConnectedCheck: tools.BoolToString(d.DisableConnectedCheck.ValueBool()), + AttributeUnchanged: api.SelectedMap(d.AttributeUnchanged.ValueString()), + PrefixListIn: api.SelectedMap(d.PrefixListIn.ValueString()), + PrefixListOut: api.SelectedMap(d.PrefixListOut.ValueString()), + RouteMapIn: api.SelectedMap(d.RouteMapIn.ValueString()), + RouteMapOut: api.SelectedMap(d.RouteMapOut.ValueString()), + }, nil +} + +func convertQuaggaBGPNeighborStructToSchema(d *quagga.BGPNeighbor) (*QuaggaBGPNeighborResourceModel, error) { + return &QuaggaBGPNeighborResourceModel{ + Enabled: types.BoolValue(tools.StringToBool(d.Enabled)), + Description: types.StringValue(d.Description), + PeerIP: types.StringValue(d.PeerIP), + RemoteAS: types.Int64Value(tools.StringToInt64(d.RemoteAS)), + Password: types.StringValue(d.Password), + Weight: types.Int64Value(tools.StringToInt64(d.Weight)), + LocalIP: types.StringValue(d.LocalIP), + UpdateSource: types.StringValue(d.UpdateSource.String()), + LinkLocalInterface: types.StringValue(d.LinkLocalInterface.String()), + NextHopSelf: types.BoolValue(tools.StringToBool(d.NextHopSelf)), + NextHopSelfAll: types.BoolValue(tools.StringToBool(d.NextHopSelfAll)), + MultiHop: types.BoolValue(tools.StringToBool(d.MultiHop)), + MultiProtocol: types.BoolValue(tools.StringToBool(d.MultiProtocol)), + RRClient: types.BoolValue(tools.StringToBool(d.RRClient)), + BFD: types.BoolValue(tools.StringToBool(d.BFD)), + KeepAlive: types.Int64Value(tools.StringToInt64(d.KeepAlive)), + HoldDown: types.Int64Value(tools.StringToInt64(d.HoldDown)), + ConnectTimer: types.Int64Value(tools.StringToInt64(d.ConnectTimer)), + DefaultRoute: types.BoolValue(tools.StringToBool(d.DefaultRoute)), + ASOverride: types.BoolValue(tools.StringToBool(d.ASOverride)), + DisableConnectedCheck: types.BoolValue(tools.StringToBool(d.DisableConnectedCheck)), + AttributeUnchanged: types.StringValue(d.AttributeUnchanged.String()), + PrefixListIn: types.StringValue(d.PrefixListIn.String()), + PrefixListOut: types.StringValue(d.PrefixListOut.String()), + RouteMapIn: types.StringValue(d.RouteMapIn.String()), + RouteMapOut: types.StringValue(d.RouteMapOut.String()), + }, nil +} From 6ff24a3ddd8d9dd088f0329a6d2edb02784433ac Mon Sep 17 00:00:00 2001 From: Luke Browning Date: Sun, 15 Oct 2023 02:38:10 -0700 Subject: [PATCH 3/7] Add BGP aslist resource & data source --- internal/provider/provider.go | 2 + .../service/quagga_bgp_aspath_data_source.go | 80 ++++++++ .../service/quagga_bgp_aspath_resource.go | 182 ++++++++++++++++++ internal/service/quagga_bgp_aspath_schema.go | 129 +++++++++++++ 4 files changed, 393 insertions(+) create mode 100644 internal/service/quagga_bgp_aspath_data_source.go create mode 100644 internal/service/quagga_bgp_aspath_resource.go create mode 100644 internal/service/quagga_bgp_aspath_schema.go diff --git a/internal/provider/provider.go b/internal/provider/provider.go index ae83266..e04d813 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -124,6 +124,7 @@ func (p *OPNsenseProvider) Resources(ctx context.Context) []func() resource.Reso service.NewWireguardClientResource, // Quagga service.NewQuaggaBGPNeighborResource, + service.NewQuaggaBGPASPathResource, // Firewall service.NewFirewallFilterResource, service.NewFirewallNATResource, @@ -148,6 +149,7 @@ func (p *OPNsenseProvider) DataSources(ctx context.Context) []func() datasource. service.NewWireguardClientDataSource, // Quagga service.NewQuaggaBGPNeighborDataSource, + service.NewQuaggaBGPASPathDataSource, // Firewall service.NewFirewallFilterDataSource, service.NewFirewallNATDataSource, diff --git a/internal/service/quagga_bgp_aspath_data_source.go b/internal/service/quagga_bgp_aspath_data_source.go new file mode 100644 index 0000000..c7ad592 --- /dev/null +++ b/internal/service/quagga_bgp_aspath_data_source.go @@ -0,0 +1,80 @@ +package service + +import ( + "context" + "fmt" + "github.com/browningluke/opnsense-go/pkg/api" + "github.com/browningluke/opnsense-go/pkg/opnsense" + "github.com/hashicorp/terraform-plugin-framework/datasource" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ datasource.DataSource = &QuaggaBGPASPathDataSource{} + +func NewQuaggaBGPASPathDataSource() datasource.DataSource { + return &QuaggaBGPASPathDataSource{} +} + +// QuaggaBGPASPathDataSource defines the data source implementation. +type QuaggaBGPASPathDataSource struct { + client opnsense.Client +} + +func (d *QuaggaBGPASPathDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_quagga_bgp_aspath" +} + +func (d *QuaggaBGPASPathDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = QuaggaBGPASPathDataSourceSchema() +} + +func (d *QuaggaBGPASPathDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + apiClient, ok := req.ProviderData.(*api.Client) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *opnsense.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + + d.client = opnsense.NewClient(apiClient) +} + +func (d *QuaggaBGPASPathDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data *QuaggaBGPASPathResourceModel + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Get resource from OPNsense API + resource, err := d.client.Quagga().GetBGPASPath(ctx, data.Id.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to read as path, got error: %s", err)) + return + } + + // Convert OPNsense struct to TF schema + resourceModel, err := convertQuaggaBGPASPathStructToSchema(resource) + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to read as path, got error: %s", err)) + return + } + + // ID cannot be added by convert... func, have to add here + resourceModel.Id = data.Id + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &resourceModel)...) +} diff --git a/internal/service/quagga_bgp_aspath_resource.go b/internal/service/quagga_bgp_aspath_resource.go new file mode 100644 index 0000000..1c8dd66 --- /dev/null +++ b/internal/service/quagga_bgp_aspath_resource.go @@ -0,0 +1,182 @@ +package service + +import ( + "context" + "errors" + "fmt" + "github.com/browningluke/opnsense-go/pkg/api" + "github.com/browningluke/opnsense-go/pkg/errs" + "github.com/browningluke/opnsense-go/pkg/opnsense" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &QuaggaBGPASPathResource{} +var _ resource.ResourceWithImportState = &QuaggaBGPASPathResource{} + +func NewQuaggaBGPASPathResource() resource.Resource { + return &QuaggaBGPASPathResource{} +} + +// QuaggaBGPASPathResource defines the resource implementation. +type QuaggaBGPASPathResource struct { + client opnsense.Client +} + +func (r *QuaggaBGPASPathResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_quagga_bgp_aspath" +} + +func (r *QuaggaBGPASPathResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = quaggaBGPASPathResourceSchema() +} + +func (r *QuaggaBGPASPathResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + apiClient, ok := req.ProviderData.(*api.Client) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *opnsense.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + + r.client = opnsense.NewClient(apiClient) +} + +func (r *QuaggaBGPASPathResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data *QuaggaBGPASPathResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Convert TF schema OPNsense struct + bgpASPath, err := convertQuaggaBGPASPathSchemaToStruct(data) + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to parse bgp aspath, got error: %s", err)) + return + } + + // Add bgp aspath to unbound + id, err := r.client.Quagga().AddBGPASPath(ctx, bgpASPath) + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to create bgp aspath, got error: %s", err)) + return + } + + // Tag new resource with ID from OPNsense + data.Id = types.StringValue(id) + + // Write logs using the tflog package + tflog.Trace(ctx, "created a resource") + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *QuaggaBGPASPathResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data *QuaggaBGPASPathResourceModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Get bgp aspath from OPNsense unbound API + bgpASPath, err := r.client.Quagga().GetBGPASPath(ctx, data.Id.ValueString()) + if err != nil { + var notFoundError *errs.NotFoundError + if errors.As(err, ¬FoundError) { + tflog.Warn(ctx, fmt.Sprintf("bgp aspath not present in remote, removing from state")) + resp.State.RemoveResource(ctx) + return + } + + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to read bgp aspath, got error: %s", err)) + return + } + + // Convert OPNsense struct to TF schema + bgpASPathModel, err := convertQuaggaBGPASPathStructToSchema(bgpASPath) + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to read bgp aspath, got error: %s", err)) + return + } + + // ID cannot be added by convert... func, have to add here + bgpASPathModel.Id = data.Id + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &bgpASPathModel)...) +} + +func (r *QuaggaBGPASPathResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data *QuaggaBGPASPathResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Convert TF schema OPNsense struct + bgpASPath, err := convertQuaggaBGPASPathSchemaToStruct(data) + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to parse bgp aspath, got error: %s", err)) + return + } + + // Update bgp aspath in unbound + err = r.client.Quagga().UpdateBGPASPath(ctx, data.Id.ValueString(), bgpASPath) + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to create bgp aspath, got error: %s", err)) + return + } + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *QuaggaBGPASPathResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data *QuaggaBGPASPathResourceModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + err := r.client.Quagga().DeleteBGPASPath(ctx, data.Id.ValueString()) + + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to delete bgp aspath, got error: %s", err)) + return + } +} + +func (r *QuaggaBGPASPathResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} diff --git a/internal/service/quagga_bgp_aspath_schema.go b/internal/service/quagga_bgp_aspath_schema.go new file mode 100644 index 0000000..e0c5011 --- /dev/null +++ b/internal/service/quagga_bgp_aspath_schema.go @@ -0,0 +1,129 @@ +package service + +import ( + "github.com/browningluke/opnsense-go/pkg/api" + "github.com/browningluke/opnsense-go/pkg/quagga" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + dschema "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "terraform-provider-opnsense/internal/tools" +) + +// QuaggaBGPASPathResourceModel describes the resource data model. +type QuaggaBGPASPathResourceModel struct { + Enabled types.Bool `tfsdk:"enabled"` + Description types.String `tfsdk:"description"` + Number types.Int64 `tfsdk:"number"` + Action types.String `tfsdk:"action"` + AS types.String `tfsdk:"as"` + + Id types.String `tfsdk:"id"` +} + +func quaggaBGPASPathResourceSchema() schema.Schema { + return schema.Schema{ + MarkdownDescription: "Configure AS Path lists for BGP.", + + Attributes: map[string]schema.Attribute{ + "enabled": schema.BoolAttribute{ + MarkdownDescription: "Enable this AS path. Defaults to `true`.", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(true), + }, + "description": schema.StringAttribute{ + MarkdownDescription: "An optional description for this AS path. Defaults to `\"\"`.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + }, + "number": schema.Int64Attribute{ + MarkdownDescription: "The ACL rule number (0-4294967294); keep in mind that there are no sequence numbers with AS-Path lists. When you want to add a new line between you have to completely remove the ACL!", + Required: true, + Validators: []validator.Int64{ + int64validator.Between(0, 4294967294), + }, + }, + "action": schema.StringAttribute{ + MarkdownDescription: "Set permit for match or deny to negate the rule. Defaults to `\"permit\"`.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString("permit"), + Validators: []validator.String{ + stringvalidator.OneOf("permit", "deny"), + }, + }, + "as": schema.StringAttribute{ + MarkdownDescription: "The AS pattern you want to match, regexp allowed (e.g. `.$` or `_1$`). It's not validated so please be careful!", + Required: true, + }, + "id": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "UUID of the AS path.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + } +} + +func QuaggaBGPASPathDataSourceSchema() dschema.Schema { + return dschema.Schema{ + MarkdownDescription: "Configure AS Path lists for BGP.", + + Attributes: map[string]dschema.Attribute{ + "id": dschema.StringAttribute{ + MarkdownDescription: "UUID of the resource.", + Required: true, + }, + "enabled": dschema.BoolAttribute{ + MarkdownDescription: "Enable this AS path.", + Computed: true, + }, + "description": dschema.StringAttribute{ + MarkdownDescription: "An optional description for this AS path.", + Computed: true, + }, + "number": dschema.Int64Attribute{ + MarkdownDescription: "The ACL rule number (0-4294967294); keep in mind that there are no sequence numbers with AS-Path lists. When you want to add a new line between you have to completely remove the ACL!", + Computed: true, + }, + "action": dschema.StringAttribute{ + MarkdownDescription: "Set permit for match or deny to negate the rule.", + Computed: true, + }, + "as": dschema.StringAttribute{ + MarkdownDescription: "The AS pattern you want to match, regexp allowed (e.g. `.$` or `_1$`). It's not validated so please be careful!", + Computed: true, + }, + }, + } +} + +func convertQuaggaBGPASPathSchemaToStruct(d *QuaggaBGPASPathResourceModel) (*quagga.BGPASPath, error) { + return &quagga.BGPASPath{ + Enabled: tools.BoolToString(d.Enabled.ValueBool()), + Description: d.Description.ValueString(), + Number: tools.Int64ToString(d.Number.ValueInt64()), + Action: api.SelectedMap(d.Action.ValueString()), + AS: d.AS.ValueString(), + }, nil +} + +func convertQuaggaBGPASPathStructToSchema(d *quagga.BGPASPath) (*QuaggaBGPASPathResourceModel, error) { + return &QuaggaBGPASPathResourceModel{ + Enabled: types.BoolValue(tools.StringToBool(d.Enabled)), + Description: types.StringValue(d.Description), + Number: types.Int64Value(tools.StringToInt64(d.Number)), + Action: types.StringValue(d.Action.String()), + AS: types.StringValue(d.AS), + }, nil +} From 78eeb0be7f847b5eeb837ace189955ae2d8135d9 Mon Sep 17 00:00:00 2001 From: Luke Browning Date: Sun, 15 Oct 2023 02:38:45 -0700 Subject: [PATCH 4/7] Add BGP prefixlist resource & data source --- internal/provider/provider.go | 2 + .../quagga_bgp_prefixlist_data_source.go | 80 ++++++++ .../service/quagga_bgp_prefixlist_resource.go | 182 ++++++++++++++++++ .../service/quagga_bgp_prefixlist_schema.go | 156 +++++++++++++++ 4 files changed, 420 insertions(+) create mode 100644 internal/service/quagga_bgp_prefixlist_data_source.go create mode 100644 internal/service/quagga_bgp_prefixlist_resource.go create mode 100644 internal/service/quagga_bgp_prefixlist_schema.go diff --git a/internal/provider/provider.go b/internal/provider/provider.go index e04d813..93ab5b6 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -125,6 +125,7 @@ func (p *OPNsenseProvider) Resources(ctx context.Context) []func() resource.Reso // Quagga service.NewQuaggaBGPNeighborResource, service.NewQuaggaBGPASPathResource, + service.NewQuaggaBGPPrefixListResource, // Firewall service.NewFirewallFilterResource, service.NewFirewallNATResource, @@ -150,6 +151,7 @@ func (p *OPNsenseProvider) DataSources(ctx context.Context) []func() datasource. // Quagga service.NewQuaggaBGPNeighborDataSource, service.NewQuaggaBGPASPathDataSource, + service.NewQuaggaBGPPrefixListDataSource, // Firewall service.NewFirewallFilterDataSource, service.NewFirewallNATDataSource, diff --git a/internal/service/quagga_bgp_prefixlist_data_source.go b/internal/service/quagga_bgp_prefixlist_data_source.go new file mode 100644 index 0000000..6b047e6 --- /dev/null +++ b/internal/service/quagga_bgp_prefixlist_data_source.go @@ -0,0 +1,80 @@ +package service + +import ( + "context" + "fmt" + "github.com/browningluke/opnsense-go/pkg/api" + "github.com/browningluke/opnsense-go/pkg/opnsense" + "github.com/hashicorp/terraform-plugin-framework/datasource" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ datasource.DataSource = &QuaggaBGPPrefixListDataSource{} + +func NewQuaggaBGPPrefixListDataSource() datasource.DataSource { + return &QuaggaBGPPrefixListDataSource{} +} + +// QuaggaBGPPrefixListDataSource defines the data source implementation. +type QuaggaBGPPrefixListDataSource struct { + client opnsense.Client +} + +func (d *QuaggaBGPPrefixListDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_quagga_bgp_prefixlist" +} + +func (d *QuaggaBGPPrefixListDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = QuaggaBGPPrefixListDataSourceSchema() +} + +func (d *QuaggaBGPPrefixListDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + apiClient, ok := req.ProviderData.(*api.Client) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *opnsense.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + + d.client = opnsense.NewClient(apiClient) +} + +func (d *QuaggaBGPPrefixListDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data *QuaggaBGPPrefixListResourceModel + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Get resource from OPNsense API + resource, err := d.client.Quagga().GetBGPPrefixList(ctx, data.Id.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to read prefix list, got error: %s", err)) + return + } + + // Convert OPNsense struct to TF schema + resourceModel, err := convertQuaggaBGPPrefixListStructToSchema(resource) + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to read prefix list, got error: %s", err)) + return + } + + // ID cannot be added by convert... func, have to add here + resourceModel.Id = data.Id + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &resourceModel)...) +} diff --git a/internal/service/quagga_bgp_prefixlist_resource.go b/internal/service/quagga_bgp_prefixlist_resource.go new file mode 100644 index 0000000..621ea19 --- /dev/null +++ b/internal/service/quagga_bgp_prefixlist_resource.go @@ -0,0 +1,182 @@ +package service + +import ( + "context" + "errors" + "fmt" + "github.com/browningluke/opnsense-go/pkg/api" + "github.com/browningluke/opnsense-go/pkg/errs" + "github.com/browningluke/opnsense-go/pkg/opnsense" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &QuaggaBGPPrefixListResource{} +var _ resource.ResourceWithImportState = &QuaggaBGPPrefixListResource{} + +func NewQuaggaBGPPrefixListResource() resource.Resource { + return &QuaggaBGPPrefixListResource{} +} + +// QuaggaBGPPrefixListResource defines the resource implementation. +type QuaggaBGPPrefixListResource struct { + client opnsense.Client +} + +func (r *QuaggaBGPPrefixListResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_quagga_bgp_prefixlist" +} + +func (r *QuaggaBGPPrefixListResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = quaggaBGPPrefixListResourceSchema() +} + +func (r *QuaggaBGPPrefixListResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + apiClient, ok := req.ProviderData.(*api.Client) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *opnsense.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + + r.client = opnsense.NewClient(apiClient) +} + +func (r *QuaggaBGPPrefixListResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data *QuaggaBGPPrefixListResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Convert TF schema OPNsense struct + bgpPrefixList, err := convertQuaggaBGPPrefixListSchemaToStruct(data) + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to parse bgp prefix list, got error: %s", err)) + return + } + + // Add bgp prefix list to unbound + id, err := r.client.Quagga().AddBGPPrefixList(ctx, bgpPrefixList) + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to create bgp prefix list, got error: %s", err)) + return + } + + // Tag new resource with ID from OPNsense + data.Id = types.StringValue(id) + + // Write logs using the tflog package + tflog.Trace(ctx, "created a resource") + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *QuaggaBGPPrefixListResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data *QuaggaBGPPrefixListResourceModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Get bgp prefix list from OPNsense unbound API + bgpPrefixList, err := r.client.Quagga().GetBGPPrefixList(ctx, data.Id.ValueString()) + if err != nil { + var notFoundError *errs.NotFoundError + if errors.As(err, ¬FoundError) { + tflog.Warn(ctx, fmt.Sprintf("bgp prefix list not present in remote, removing from state")) + resp.State.RemoveResource(ctx) + return + } + + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to read bgp prefix list, got error: %s", err)) + return + } + + // Convert OPNsense struct to TF schema + bgpPrefixListModel, err := convertQuaggaBGPPrefixListStructToSchema(bgpPrefixList) + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to read bgp prefix list, got error: %s", err)) + return + } + + // ID cannot be added by convert... func, have to add here + bgpPrefixListModel.Id = data.Id + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &bgpPrefixListModel)...) +} + +func (r *QuaggaBGPPrefixListResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data *QuaggaBGPPrefixListResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Convert TF schema OPNsense struct + bgpPrefixList, err := convertQuaggaBGPPrefixListSchemaToStruct(data) + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to parse bgp prefix list, got error: %s", err)) + return + } + + // Update bgp prefix list in unbound + err = r.client.Quagga().UpdateBGPPrefixList(ctx, data.Id.ValueString(), bgpPrefixList) + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to create bgp prefix list, got error: %s", err)) + return + } + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *QuaggaBGPPrefixListResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data *QuaggaBGPPrefixListResourceModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + err := r.client.Quagga().DeleteBGPPrefixList(ctx, data.Id.ValueString()) + + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to delete bgp prefix list, got error: %s", err)) + return + } +} + +func (r *QuaggaBGPPrefixListResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} diff --git a/internal/service/quagga_bgp_prefixlist_schema.go b/internal/service/quagga_bgp_prefixlist_schema.go new file mode 100644 index 0000000..710ee2a --- /dev/null +++ b/internal/service/quagga_bgp_prefixlist_schema.go @@ -0,0 +1,156 @@ +package service + +import ( + "github.com/browningluke/opnsense-go/pkg/api" + "github.com/browningluke/opnsense-go/pkg/quagga" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + dschema "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "terraform-provider-opnsense/internal/tools" +) + +// QuaggaBGPPrefixListResourceModel describes the resource data model. +type QuaggaBGPPrefixListResourceModel struct { + Enabled types.Bool `tfsdk:"enabled"` + Description types.String `tfsdk:"description"` + Name types.String `tfsdk:"name"` + IPVersion types.String `tfsdk:"ip_version"` + Number types.Int64 `tfsdk:"number"` + Action types.String `tfsdk:"action"` + Network types.String `tfsdk:"network"` + + Id types.String `tfsdk:"id"` +} + +func quaggaBGPPrefixListResourceSchema() schema.Schema { + return schema.Schema{ + MarkdownDescription: "Configure prefix lists for BGP.", + + Attributes: map[string]schema.Attribute{ + "enabled": schema.BoolAttribute{ + MarkdownDescription: "Enable this prefix list. Defaults to `true`.", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(true), + }, + "description": schema.StringAttribute{ + MarkdownDescription: "An optional description for this prefix list. Defaults to `\"\"`.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + }, + "name": schema.StringAttribute{ + MarkdownDescription: "The name of this prefix list.", + Required: true, + }, + "ip_version": schema.StringAttribute{ + MarkdownDescription: "Set the IP version to use. Defaults to `\"IPv4\"`.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString("IPv4"), + Validators: []validator.String{ + stringvalidator.OneOf("IPv4", "IPv6"), + }, + }, + "number": schema.Int64Attribute{ + MarkdownDescription: "The ACL sequence number (1-4294967294).", + Required: true, + Validators: []validator.Int64{ + int64validator.Between(0, 4294967294), + }, + }, + "action": schema.StringAttribute{ + MarkdownDescription: "Set permit for match or deny to negate the rule. Defaults to `\"permit\"`.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString("permit"), + Validators: []validator.String{ + stringvalidator.OneOf("permit", "deny"), + }, + }, + "network": schema.StringAttribute{ + MarkdownDescription: "The network pattern you want to match. You can also add \"ge\" or \"le\" additions after the network statement. It's not validated so please be careful!", + Required: true, + }, + "id": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "UUID of the prefix list.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + } +} + +func QuaggaBGPPrefixListDataSourceSchema() dschema.Schema { + return dschema.Schema{ + MarkdownDescription: "Configure prefix lists for BGP.", + + Attributes: map[string]dschema.Attribute{ + "id": dschema.StringAttribute{ + MarkdownDescription: "UUID of the resource.", + Required: true, + }, + "enabled": dschema.BoolAttribute{ + MarkdownDescription: "Enable this prefix list.", + Computed: true, + }, + "description": dschema.StringAttribute{ + MarkdownDescription: "An optional description for this prefix list.", + Computed: true, + }, + "name": dschema.StringAttribute{ + MarkdownDescription: "The name of this prefix list.", + Computed: true, + }, + "ip_version": dschema.StringAttribute{ + MarkdownDescription: "Set the IP version to use.", + Computed: true, + }, + "number": dschema.Int64Attribute{ + MarkdownDescription: "The ACL sequence number (1-4294967294).", + Computed: true, + }, + "action": dschema.StringAttribute{ + MarkdownDescription: "Set permit for match or deny to negate the rule.", + Computed: true, + }, + "network": dschema.StringAttribute{ + MarkdownDescription: "The network pattern you want to match. You can also add \"ge\" or \"le\" additions after the network statement. It's not validated so please be careful!", + Computed: true, + }, + }, + } +} + +func convertQuaggaBGPPrefixListSchemaToStruct(d *QuaggaBGPPrefixListResourceModel) (*quagga.BGPPrefixList, error) { + return &quagga.BGPPrefixList{ + Enabled: tools.BoolToString(d.Enabled.ValueBool()), + Description: d.Description.ValueString(), + Name: d.Name.ValueString(), + IPVersion: api.SelectedMap(d.IPVersion.ValueString()), + SequenceNumber: tools.Int64ToString(d.Number.ValueInt64()), + Action: api.SelectedMap(d.Action.ValueString()), + Network: d.Network.ValueString(), + }, nil +} + +func convertQuaggaBGPPrefixListStructToSchema(d *quagga.BGPPrefixList) (*QuaggaBGPPrefixListResourceModel, error) { + return &QuaggaBGPPrefixListResourceModel{ + Enabled: types.BoolValue(tools.StringToBool(d.Enabled)), + Description: types.StringValue(d.Description), + Name: types.StringValue(d.Name), + IPVersion: types.StringValue(d.IPVersion.String()), + Number: types.Int64Value(tools.StringToInt64(d.SequenceNumber)), + Action: types.StringValue(d.Action.String()), + Network: types.StringValue(d.Network), + }, nil +} From 1e418b4aaacffe0c13011ced7096c908d342765e Mon Sep 17 00:00:00 2001 From: Luke Browning Date: Sun, 15 Oct 2023 02:39:09 -0700 Subject: [PATCH 5/7] Add BGP communitylist resource & data source --- internal/provider/provider.go | 2 + .../quagga_bgp_communitylist_data_source.go | 80 ++++++++ .../quagga_bgp_communitylist_resource.go | 182 ++++++++++++++++++ .../quagga_bgp_communitylist_schema.go | 143 ++++++++++++++ 4 files changed, 407 insertions(+) create mode 100644 internal/service/quagga_bgp_communitylist_data_source.go create mode 100644 internal/service/quagga_bgp_communitylist_resource.go create mode 100644 internal/service/quagga_bgp_communitylist_schema.go diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 93ab5b6..a2daa3d 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -126,6 +126,7 @@ func (p *OPNsenseProvider) Resources(ctx context.Context) []func() resource.Reso service.NewQuaggaBGPNeighborResource, service.NewQuaggaBGPASPathResource, service.NewQuaggaBGPPrefixListResource, + service.NewQuaggaBGPCommunityListResource, // Firewall service.NewFirewallFilterResource, service.NewFirewallNATResource, @@ -152,6 +153,7 @@ func (p *OPNsenseProvider) DataSources(ctx context.Context) []func() datasource. service.NewQuaggaBGPNeighborDataSource, service.NewQuaggaBGPASPathDataSource, service.NewQuaggaBGPPrefixListDataSource, + service.NewQuaggaBGPCommunityListDataSource, // Firewall service.NewFirewallFilterDataSource, service.NewFirewallNATDataSource, diff --git a/internal/service/quagga_bgp_communitylist_data_source.go b/internal/service/quagga_bgp_communitylist_data_source.go new file mode 100644 index 0000000..74a20e0 --- /dev/null +++ b/internal/service/quagga_bgp_communitylist_data_source.go @@ -0,0 +1,80 @@ +package service + +import ( + "context" + "fmt" + "github.com/browningluke/opnsense-go/pkg/api" + "github.com/browningluke/opnsense-go/pkg/opnsense" + "github.com/hashicorp/terraform-plugin-framework/datasource" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ datasource.DataSource = &QuaggaBGPCommunityListDataSource{} + +func NewQuaggaBGPCommunityListDataSource() datasource.DataSource { + return &QuaggaBGPCommunityListDataSource{} +} + +// QuaggaBGPCommunityListDataSource defines the data source implementation. +type QuaggaBGPCommunityListDataSource struct { + client opnsense.Client +} + +func (d *QuaggaBGPCommunityListDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_quagga_bgp_communitylist" +} + +func (d *QuaggaBGPCommunityListDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = QuaggaBGPCommunityListDataSourceSchema() +} + +func (d *QuaggaBGPCommunityListDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + apiClient, ok := req.ProviderData.(*api.Client) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *opnsense.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + + d.client = opnsense.NewClient(apiClient) +} + +func (d *QuaggaBGPCommunityListDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data *QuaggaBGPCommunityListResourceModel + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Get resource from OPNsense API + resource, err := d.client.Quagga().GetBGPCommunityList(ctx, data.Id.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to read community list, got error: %s", err)) + return + } + + // Convert OPNsense struct to TF schema + resourceModel, err := convertQuaggaBGPCommunityListStructToSchema(resource) + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to read community list, got error: %s", err)) + return + } + + // ID cannot be added by convert... func, have to add here + resourceModel.Id = data.Id + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &resourceModel)...) +} diff --git a/internal/service/quagga_bgp_communitylist_resource.go b/internal/service/quagga_bgp_communitylist_resource.go new file mode 100644 index 0000000..75b7a32 --- /dev/null +++ b/internal/service/quagga_bgp_communitylist_resource.go @@ -0,0 +1,182 @@ +package service + +import ( + "context" + "errors" + "fmt" + "github.com/browningluke/opnsense-go/pkg/api" + "github.com/browningluke/opnsense-go/pkg/errs" + "github.com/browningluke/opnsense-go/pkg/opnsense" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &QuaggaBGPCommunityListResource{} +var _ resource.ResourceWithImportState = &QuaggaBGPCommunityListResource{} + +func NewQuaggaBGPCommunityListResource() resource.Resource { + return &QuaggaBGPCommunityListResource{} +} + +// QuaggaBGPCommunityListResource defines the resource implementation. +type QuaggaBGPCommunityListResource struct { + client opnsense.Client +} + +func (r *QuaggaBGPCommunityListResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_quagga_bgp_communitylist" +} + +func (r *QuaggaBGPCommunityListResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = quaggaBGPCommunityListResourceSchema() +} + +func (r *QuaggaBGPCommunityListResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + apiClient, ok := req.ProviderData.(*api.Client) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *opnsense.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + + r.client = opnsense.NewClient(apiClient) +} + +func (r *QuaggaBGPCommunityListResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data *QuaggaBGPCommunityListResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Convert TF schema OPNsense struct + bgpCommunityList, err := convertQuaggaBGPCommunityListSchemaToStruct(data) + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to parse bgp community list, got error: %s", err)) + return + } + + // Add bgp community list to unbound + id, err := r.client.Quagga().AddBGPCommunityList(ctx, bgpCommunityList) + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to create bgp community list, got error: %s", err)) + return + } + + // Tag new resource with ID from OPNsense + data.Id = types.StringValue(id) + + // Write logs using the tflog package + tflog.Trace(ctx, "created a resource") + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *QuaggaBGPCommunityListResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data *QuaggaBGPCommunityListResourceModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Get bgp community list from OPNsense unbound API + bgpCommunityList, err := r.client.Quagga().GetBGPCommunityList(ctx, data.Id.ValueString()) + if err != nil { + var notFoundError *errs.NotFoundError + if errors.As(err, ¬FoundError) { + tflog.Warn(ctx, fmt.Sprintf("bgp community list not present in remote, removing from state")) + resp.State.RemoveResource(ctx) + return + } + + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to read bgp community list, got error: %s", err)) + return + } + + // Convert OPNsense struct to TF schema + bgpCommunityListModel, err := convertQuaggaBGPCommunityListStructToSchema(bgpCommunityList) + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to read bgp community list, got error: %s", err)) + return + } + + // ID cannot be added by convert... func, have to add here + bgpCommunityListModel.Id = data.Id + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &bgpCommunityListModel)...) +} + +func (r *QuaggaBGPCommunityListResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data *QuaggaBGPCommunityListResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Convert TF schema OPNsense struct + bgpCommunityList, err := convertQuaggaBGPCommunityListSchemaToStruct(data) + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to parse bgp community list, got error: %s", err)) + return + } + + // Update bgp community list in unbound + err = r.client.Quagga().UpdateBGPCommunityList(ctx, data.Id.ValueString(), bgpCommunityList) + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to create bgp community list, got error: %s", err)) + return + } + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *QuaggaBGPCommunityListResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data *QuaggaBGPCommunityListResourceModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + err := r.client.Quagga().DeleteBGPCommunityList(ctx, data.Id.ValueString()) + + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to delete bgp community list, got error: %s", err)) + return + } +} + +func (r *QuaggaBGPCommunityListResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} diff --git a/internal/service/quagga_bgp_communitylist_schema.go b/internal/service/quagga_bgp_communitylist_schema.go new file mode 100644 index 0000000..4fb7c21 --- /dev/null +++ b/internal/service/quagga_bgp_communitylist_schema.go @@ -0,0 +1,143 @@ +package service + +import ( + "github.com/browningluke/opnsense-go/pkg/api" + "github.com/browningluke/opnsense-go/pkg/quagga" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + dschema "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "terraform-provider-opnsense/internal/tools" +) + +// QuaggaBGPCommunityListResourceModel describes the resource data model. +type QuaggaBGPCommunityListResourceModel struct { + Enabled types.Bool `tfsdk:"enabled"` + Description types.String `tfsdk:"description"` + Number types.Int64 `tfsdk:"number"` + SequenceNumber types.Int64 `tfsdk:"seq_number"` + Action types.String `tfsdk:"action"` + Community types.String `tfsdk:"community"` + + Id types.String `tfsdk:"id"` +} + +func quaggaBGPCommunityListResourceSchema() schema.Schema { + return schema.Schema{ + MarkdownDescription: "Configure community lists for BGP.", + + Attributes: map[string]schema.Attribute{ + "enabled": schema.BoolAttribute{ + MarkdownDescription: "Enable this community list. Defaults to `true`.", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(true), + }, + "description": schema.StringAttribute{ + MarkdownDescription: "An optional description for this prefix list. Defaults to `\"\"`.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + }, + "number": schema.Int64Attribute{ + MarkdownDescription: "Set the number of your Community-List. 1-99 are standard lists while 100-500 are expanded lists.", + Required: true, + Validators: []validator.Int64{ + int64validator.Between(1, 500), + }, + }, + "seq_number": schema.Int64Attribute{ + MarkdownDescription: "The ACL sequence number (10-99).", + Required: true, + Validators: []validator.Int64{ + int64validator.Between(10, 99), + }, + }, + "action": schema.StringAttribute{ + MarkdownDescription: "Set permit for match or deny to negate the rule. Defaults to `\"permit\"`.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString("permit"), + Validators: []validator.String{ + stringvalidator.OneOf("permit", "deny"), + }, + }, + "community": schema.StringAttribute{ + MarkdownDescription: "The community you want to match. You can also regex and it is not validated so please be careful.", + Required: true, + }, + "id": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "UUID of the community list.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + } +} + +func QuaggaBGPCommunityListDataSourceSchema() dschema.Schema { + return dschema.Schema{ + MarkdownDescription: "Configure community lists for BGP.", + + Attributes: map[string]dschema.Attribute{ + "id": dschema.StringAttribute{ + MarkdownDescription: "UUID of the resource.", + Required: true, + }, + "enabled": schema.BoolAttribute{ + MarkdownDescription: "Enable this community list.", + Computed: true, + }, + "description": schema.StringAttribute{ + MarkdownDescription: "An optional description for this prefix list.", + Computed: true, + }, + "number": schema.Int64Attribute{ + MarkdownDescription: "Set the number of your Community-List. 1-99 are standard lists while 100-500 are expanded lists.", + Computed: true, + }, + "seq_number": schema.Int64Attribute{ + MarkdownDescription: "The ACL sequence number (10-99).", + Computed: true, + }, + "action": schema.StringAttribute{ + MarkdownDescription: "Set permit for match or deny to negate the rule.", + Computed: true, + }, + "community": schema.StringAttribute{ + MarkdownDescription: "The community you want to match. You can also regex and it is not validated so please be careful.", + Computed: true, + }, + }, + } +} + +func convertQuaggaBGPCommunityListSchemaToStruct(d *QuaggaBGPCommunityListResourceModel) (*quagga.BGPCommunityList, error) { + return &quagga.BGPCommunityList{ + Enabled: tools.BoolToString(d.Enabled.ValueBool()), + Description: d.Description.ValueString(), + Number: tools.Int64ToString(d.Number.ValueInt64()), + SequenceNumber: tools.Int64ToString(d.SequenceNumber.ValueInt64()), + Action: api.SelectedMap(d.Action.ValueString()), + Community: d.Community.ValueString(), + }, nil +} + +func convertQuaggaBGPCommunityListStructToSchema(d *quagga.BGPCommunityList) (*QuaggaBGPCommunityListResourceModel, error) { + return &QuaggaBGPCommunityListResourceModel{ + Enabled: types.BoolValue(tools.StringToBool(d.Enabled)), + Description: types.StringValue(d.Description), + Number: types.Int64Value(tools.StringToInt64(d.Number)), + SequenceNumber: types.Int64Value(tools.StringToInt64(d.SequenceNumber)), + Action: types.StringValue(d.Action.String()), + Community: types.StringValue(d.Community), + }, nil +} From cc9c916cd22c00db94719430f4617fbc8ae45078 Mon Sep 17 00:00:00 2001 From: Luke Browning Date: Sun, 15 Oct 2023 02:39:23 -0700 Subject: [PATCH 6/7] Add BGP routemap resource & data source --- internal/provider/provider.go | 2 + .../quagga_bgp_routemap_data_source.go | 80 +++++++ .../service/quagga_bgp_routemap_resource.go | 182 +++++++++++++++ .../service/quagga_bgp_routemap_schema.go | 209 ++++++++++++++++++ 4 files changed, 473 insertions(+) create mode 100644 internal/service/quagga_bgp_routemap_data_source.go create mode 100644 internal/service/quagga_bgp_routemap_resource.go create mode 100644 internal/service/quagga_bgp_routemap_schema.go diff --git a/internal/provider/provider.go b/internal/provider/provider.go index a2daa3d..1641000 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -127,6 +127,7 @@ func (p *OPNsenseProvider) Resources(ctx context.Context) []func() resource.Reso service.NewQuaggaBGPASPathResource, service.NewQuaggaBGPPrefixListResource, service.NewQuaggaBGPCommunityListResource, + service.NewQuaggaBGPRouteMapResource, // Firewall service.NewFirewallFilterResource, service.NewFirewallNATResource, @@ -154,6 +155,7 @@ func (p *OPNsenseProvider) DataSources(ctx context.Context) []func() datasource. service.NewQuaggaBGPASPathDataSource, service.NewQuaggaBGPPrefixListDataSource, service.NewQuaggaBGPCommunityListDataSource, + service.NewQuaggaBGPRouteMapDataSource, // Firewall service.NewFirewallFilterDataSource, service.NewFirewallNATDataSource, diff --git a/internal/service/quagga_bgp_routemap_data_source.go b/internal/service/quagga_bgp_routemap_data_source.go new file mode 100644 index 0000000..fe7768b --- /dev/null +++ b/internal/service/quagga_bgp_routemap_data_source.go @@ -0,0 +1,80 @@ +package service + +import ( + "context" + "fmt" + "github.com/browningluke/opnsense-go/pkg/api" + "github.com/browningluke/opnsense-go/pkg/opnsense" + "github.com/hashicorp/terraform-plugin-framework/datasource" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ datasource.DataSource = &QuaggaBGPRouteMapDataSource{} + +func NewQuaggaBGPRouteMapDataSource() datasource.DataSource { + return &QuaggaBGPRouteMapDataSource{} +} + +// QuaggaBGPRouteMapDataSource defines the data source implementation. +type QuaggaBGPRouteMapDataSource struct { + client opnsense.Client +} + +func (d *QuaggaBGPRouteMapDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_quagga_bgp_routemap" +} + +func (d *QuaggaBGPRouteMapDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = QuaggaBGPRouteMapDataSourceSchema() +} + +func (d *QuaggaBGPRouteMapDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + apiClient, ok := req.ProviderData.(*api.Client) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *opnsense.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + + d.client = opnsense.NewClient(apiClient) +} + +func (d *QuaggaBGPRouteMapDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data *QuaggaBGPRouteMapResourceModel + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Get resource from OPNsense API + resource, err := d.client.Quagga().GetBGPRouteMap(ctx, data.Id.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to read route map, got error: %s", err)) + return + } + + // Convert OPNsense struct to TF schema + resourceModel, err := convertQuaggaBGPRouteMapStructToSchema(resource) + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to read route map, got error: %s", err)) + return + } + + // ID cannot be added by convert... func, have to add here + resourceModel.Id = data.Id + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &resourceModel)...) +} diff --git a/internal/service/quagga_bgp_routemap_resource.go b/internal/service/quagga_bgp_routemap_resource.go new file mode 100644 index 0000000..b2d3c1d --- /dev/null +++ b/internal/service/quagga_bgp_routemap_resource.go @@ -0,0 +1,182 @@ +package service + +import ( + "context" + "errors" + "fmt" + "github.com/browningluke/opnsense-go/pkg/api" + "github.com/browningluke/opnsense-go/pkg/errs" + "github.com/browningluke/opnsense-go/pkg/opnsense" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &QuaggaBGPRouteMapResource{} +var _ resource.ResourceWithImportState = &QuaggaBGPRouteMapResource{} + +func NewQuaggaBGPRouteMapResource() resource.Resource { + return &QuaggaBGPRouteMapResource{} +} + +// QuaggaBGPRouteMapResource defines the resource implementation. +type QuaggaBGPRouteMapResource struct { + client opnsense.Client +} + +func (r *QuaggaBGPRouteMapResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_quagga_bgp_routemap" +} + +func (r *QuaggaBGPRouteMapResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = quaggaBGPRouteMapResourceSchema() +} + +func (r *QuaggaBGPRouteMapResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + apiClient, ok := req.ProviderData.(*api.Client) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *opnsense.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + + r.client = opnsense.NewClient(apiClient) +} + +func (r *QuaggaBGPRouteMapResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data *QuaggaBGPRouteMapResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Convert TF schema OPNsense struct + bgpRouteMap, err := convertQuaggaBGPRouteMapSchemaToStruct(data) + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to parse bgp route map, got error: %s", err)) + return + } + + // Add bgp route map to unbound + id, err := r.client.Quagga().AddBGPRouteMap(ctx, bgpRouteMap) + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to create bgp route map, got error: %s", err)) + return + } + + // Tag new resource with ID from OPNsense + data.Id = types.StringValue(id) + + // Write logs using the tflog package + tflog.Trace(ctx, "created a resource") + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *QuaggaBGPRouteMapResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data *QuaggaBGPRouteMapResourceModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Get bgp route map from OPNsense unbound API + bgpRouteMap, err := r.client.Quagga().GetBGPRouteMap(ctx, data.Id.ValueString()) + if err != nil { + var notFoundError *errs.NotFoundError + if errors.As(err, ¬FoundError) { + tflog.Warn(ctx, fmt.Sprintf("bgp route map not present in remote, removing from state")) + resp.State.RemoveResource(ctx) + return + } + + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to read bgp route map, got error: %s", err)) + return + } + + // Convert OPNsense struct to TF schema + bgpRouteMapModel, err := convertQuaggaBGPRouteMapStructToSchema(bgpRouteMap) + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to read bgp route map, got error: %s", err)) + return + } + + // ID cannot be added by convert... func, have to add here + bgpRouteMapModel.Id = data.Id + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &bgpRouteMapModel)...) +} + +func (r *QuaggaBGPRouteMapResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data *QuaggaBGPRouteMapResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Convert TF schema OPNsense struct + bgpRouteMap, err := convertQuaggaBGPRouteMapSchemaToStruct(data) + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to parse bgp route map, got error: %s", err)) + return + } + + // Update bgp route map in unbound + err = r.client.Quagga().UpdateBGPRouteMap(ctx, data.Id.ValueString(), bgpRouteMap) + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to create bgp route map, got error: %s", err)) + return + } + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *QuaggaBGPRouteMapResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data *QuaggaBGPRouteMapResourceModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + err := r.client.Quagga().DeleteBGPRouteMap(ctx, data.Id.ValueString()) + + if err != nil { + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to delete bgp route map, got error: %s", err)) + return + } +} + +func (r *QuaggaBGPRouteMapResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} diff --git a/internal/service/quagga_bgp_routemap_schema.go b/internal/service/quagga_bgp_routemap_schema.go new file mode 100644 index 0000000..81108c9 --- /dev/null +++ b/internal/service/quagga_bgp_routemap_schema.go @@ -0,0 +1,209 @@ +package service + +import ( + "context" + "github.com/browningluke/opnsense-go/pkg/api" + "github.com/browningluke/opnsense-go/pkg/quagga" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + dschema "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/setdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "terraform-provider-opnsense/internal/tools" +) + +// QuaggaBGPRouteMapResourceModel describes the resource data model. +type QuaggaBGPRouteMapResourceModel struct { + Enabled types.Bool `tfsdk:"enabled"` + Description types.String `tfsdk:"description"` + Name types.String `tfsdk:"name"` + Action types.String `tfsdk:"action"` + RouteMapID types.Int64 `tfsdk:"route_map_id"` + ASPathList types.Set `tfsdk:"aspaths"` + PrefixList types.Set `tfsdk:"prefix_lists"` + CommunityList types.Set `tfsdk:"community_lists"` + Set types.String `tfsdk:"set"` + + Id types.String `tfsdk:"id"` +} + +func quaggaBGPRouteMapResourceSchema() schema.Schema { + return schema.Schema{ + MarkdownDescription: "Configure route maps for BGP.", + + Attributes: map[string]schema.Attribute{ + "enabled": schema.BoolAttribute{ + MarkdownDescription: "Enable this route map. Defaults to `true`.", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(true), + }, + "description": schema.StringAttribute{ + MarkdownDescription: "An optional description for this route map. Defaults to `\"\"`.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + }, + "name": schema.StringAttribute{ + MarkdownDescription: "The name of this route map.", + Required: true, + }, + "action": schema.StringAttribute{ + MarkdownDescription: "Set permit for match or deny to negate the rule. Defaults to `\"permit\"`.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString("permit"), + Validators: []validator.String{ + stringvalidator.OneOf("permit", "deny"), + }, + }, + "route_map_id": schema.Int64Attribute{ + MarkdownDescription: "The Route-map ID between 1 and 65535. Be aware that the sorting will be done under the hood, so when you add an entry between it gets to the right position.", + Required: true, + Validators: []validator.Int64{ + int64validator.Between(1, 65535), + }, + }, + "aspaths": schema.SetAttribute{ + MarkdownDescription: "Set the AS Path list IDs to use. Defaults to `[]`.", + Optional: true, + Computed: true, + Default: setdefault.StaticValue(tools.EmptySetValue()), + ElementType: types.StringType, + }, + "prefix_lists": schema.SetAttribute{ + MarkdownDescription: "Set the prefix list IDs to use. Defaults to `[]`.", + Optional: true, + Computed: true, + Default: setdefault.StaticValue(tools.EmptySetValue()), + ElementType: types.StringType, + }, + "community_lists": schema.SetAttribute{ + MarkdownDescription: "Set the community list IDs to use. Defaults to `[]`.", + Optional: true, + Computed: true, + Default: setdefault.StaticValue(tools.EmptySetValue()), + ElementType: types.StringType, + }, + "set": schema.StringAttribute{ + MarkdownDescription: "Free text field for your set, please be careful! You can set e.g. `local-preference 300` or `community 1:1` (http://www.nongnu.org/quagga/docs/docs-multi/Route-Map-Set-Command.html#Route-Map-Set-Command). Defaults to `\"\"`.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + }, + "id": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "UUID of the route map.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + } +} + +func QuaggaBGPRouteMapDataSourceSchema() dschema.Schema { + return dschema.Schema{ + MarkdownDescription: "Configure route maps for BGP.", + + Attributes: map[string]dschema.Attribute{ + "id": dschema.StringAttribute{ + MarkdownDescription: "UUID of the resource.", + Required: true, + }, + "enabled": dschema.BoolAttribute{ + MarkdownDescription: "Enable this route map.", + Computed: true, + }, + "description": dschema.StringAttribute{ + MarkdownDescription: "An optional description for this route map.", + Computed: true, + }, + "name": dschema.StringAttribute{ + MarkdownDescription: "The name of this route map.", + Computed: true, + }, + "action": dschema.StringAttribute{ + MarkdownDescription: "Set permit for match or deny to negate the rule.", + Computed: true, + }, + "route_map_id": dschema.Int64Attribute{ + MarkdownDescription: "The Route-map ID between 1 and 65535. Be aware that the sorting will be done under the hood, so when you add an entry between it gets to the right position.", + Computed: true, + }, + "aspaths": dschema.SetAttribute{ + MarkdownDescription: "Set the AS Path list IDs to use.", + Computed: true, + ElementType: types.StringType, + }, + "prefix_lists": dschema.SetAttribute{ + MarkdownDescription: "Set the prefix list IDs to use.", + Computed: true, + ElementType: types.StringType, + }, + "community_lists": dschema.SetAttribute{ + MarkdownDescription: "Set the community list IDs to use.", + Computed: true, + ElementType: types.StringType, + }, + "set": dschema.StringAttribute{ + MarkdownDescription: "Free text field for your set, please be careful! You can set e.g. `local-preference 300` or `community 1:1` (http://www.nongnu.org/quagga/docs/docs-multi/Route-Map-Set-Command.html#Route-Map-Set-Command). Defaults to `\"\"`.", + Computed: true, + }, + }, + } +} + +func convertQuaggaBGPRouteMapSchemaToStruct(d *QuaggaBGPRouteMapResourceModel) (*quagga.BGPRouteMap, error) { + // Parse 'ASPathList' + var asPathList []string + d.ASPathList.ElementsAs(context.Background(), &asPathList, false) + + // Parse 'PrefixList' + var prefixList []string + d.PrefixList.ElementsAs(context.Background(), &prefixList, false) + + // Parse 'CommunityList' + var communityList []string + d.CommunityList.ElementsAs(context.Background(), &communityList, false) + + return &quagga.BGPRouteMap{ + Enabled: tools.BoolToString(d.Enabled.ValueBool()), + Description: d.Description.ValueString(), + Name: d.Name.ValueString(), + Action: api.SelectedMap(d.Action.ValueString()), + RouteMapID: tools.Int64ToString(d.RouteMapID.ValueInt64()), + ASPathList: asPathList, + PrefixList: prefixList, + CommunityList: communityList, + Set: d.Set.ValueString(), + }, nil +} + +func convertQuaggaBGPRouteMapStructToSchema(d *quagga.BGPRouteMap) (*QuaggaBGPRouteMapResourceModel, error) { + model := &QuaggaBGPRouteMapResourceModel{ + Enabled: types.BoolValue(tools.StringToBool(d.Enabled)), + Description: types.StringValue(d.Description), + Name: types.StringValue(d.Name), + Action: types.StringValue(d.Action.String()), + RouteMapID: types.Int64Value(tools.StringToInt64(d.RouteMapID)), + Set: types.StringValue(d.Set), + } + + // Parse 'ASPathList' + model.ASPathList = tools.StringSliceToSet(d.ASPathList) + + // Parse 'PrefixList' + model.PrefixList = tools.StringSliceToSet(d.PrefixList) + + // Parse 'CommunityList' + model.CommunityList = tools.StringSliceToSet(d.CommunityList) + + return model, nil +} From 86008e21e949261a163b844a1b85598b4c8a9ba1 Mon Sep 17 00:00:00 2001 From: Luke Browning Date: Sun, 15 Oct 2023 02:39:51 -0700 Subject: [PATCH 7/7] Add templates, examples & docs --- docs/data-sources/quagga_bgp_aspath.md | 26 ++++ docs/data-sources/quagga_bgp_communitylist.md | 27 +++++ docs/data-sources/quagga_bgp_neighbor.md | 47 ++++++++ docs/data-sources/quagga_bgp_prefixlist.md | 28 +++++ docs/data-sources/quagga_bgp_routemap.md | 30 +++++ docs/resources/quagga_bgp_aspath.md | 44 +++++++ docs/resources/quagga_bgp_communitylist.md | 46 +++++++ docs/resources/quagga_bgp_neighbor.md | 114 ++++++++++++++++++ docs/resources/quagga_bgp_prefixlist.md | 48 ++++++++ docs/resources/quagga_bgp_routemap.md | 97 +++++++++++++++ .../opnsense_quagga_bgp_aspath/resource.tf | 10 ++ .../resource.tf | 11 ++ .../opnsense_quagga_bgp_neighbor/resource.tf | 59 +++++++++ .../resource.tf | 12 ++ .../opnsense_quagga_bgp_routemap/resource.tf | 59 +++++++++ .../data-sources/quagga_bgp_aspath.md.tmpl | 20 +++ .../quagga_bgp_communitylist.md.tmpl | 20 +++ .../data-sources/quagga_bgp_neighbor.md.tmpl | 20 +++ .../quagga_bgp_prefixlist.md.tmpl | 20 +++ .../data-sources/quagga_bgp_routemap.md.tmpl | 20 +++ templates/resources/quagga_bgp_aspath.md.tmpl | 24 ++++ .../quagga_bgp_communitylist.md.tmpl | 24 ++++ .../resources/quagga_bgp_neighbor.md.tmpl | 24 ++++ .../resources/quagga_bgp_prefixlist.md.tmpl | 24 ++++ .../resources/quagga_bgp_routemap.md.tmpl | 24 ++++ 25 files changed, 878 insertions(+) create mode 100644 docs/data-sources/quagga_bgp_aspath.md create mode 100644 docs/data-sources/quagga_bgp_communitylist.md create mode 100644 docs/data-sources/quagga_bgp_neighbor.md create mode 100644 docs/data-sources/quagga_bgp_prefixlist.md create mode 100644 docs/data-sources/quagga_bgp_routemap.md create mode 100644 docs/resources/quagga_bgp_aspath.md create mode 100644 docs/resources/quagga_bgp_communitylist.md create mode 100644 docs/resources/quagga_bgp_neighbor.md create mode 100644 docs/resources/quagga_bgp_prefixlist.md create mode 100644 docs/resources/quagga_bgp_routemap.md create mode 100644 examples/resources/opnsense_quagga_bgp_aspath/resource.tf create mode 100644 examples/resources/opnsense_quagga_bgp_communitylist/resource.tf create mode 100644 examples/resources/opnsense_quagga_bgp_neighbor/resource.tf create mode 100644 examples/resources/opnsense_quagga_bgp_prefixlist/resource.tf create mode 100644 examples/resources/opnsense_quagga_bgp_routemap/resource.tf create mode 100644 templates/data-sources/quagga_bgp_aspath.md.tmpl create mode 100644 templates/data-sources/quagga_bgp_communitylist.md.tmpl create mode 100644 templates/data-sources/quagga_bgp_neighbor.md.tmpl create mode 100644 templates/data-sources/quagga_bgp_prefixlist.md.tmpl create mode 100644 templates/data-sources/quagga_bgp_routemap.md.tmpl create mode 100644 templates/resources/quagga_bgp_aspath.md.tmpl create mode 100644 templates/resources/quagga_bgp_communitylist.md.tmpl create mode 100644 templates/resources/quagga_bgp_neighbor.md.tmpl create mode 100644 templates/resources/quagga_bgp_prefixlist.md.tmpl create mode 100644 templates/resources/quagga_bgp_routemap.md.tmpl diff --git a/docs/data-sources/quagga_bgp_aspath.md b/docs/data-sources/quagga_bgp_aspath.md new file mode 100644 index 0000000..3194286 --- /dev/null +++ b/docs/data-sources/quagga_bgp_aspath.md @@ -0,0 +1,26 @@ +--- +page_title: "opnsense_quagga_bgp_aspath Data Source - terraform-provider-opnsense" +subcategory: Quagga +description: |- + Configure AS Path lists for BGP. +--- + +# opnsense_quagga_bgp_aspath (Data Source) + +Configure AS Path lists for BGP. + + +## Schema + +### Required + +- `id` (String) UUID of the resource. + +### Read-Only + +- `action` (String) Set permit for match or deny to negate the rule. +- `as` (String) The AS pattern you want to match, regexp allowed (e.g. `.$` or `_1$`). It's not validated so please be careful! +- `description` (String) An optional description for this AS path. +- `enabled` (Boolean) Enable this AS path. +- `number` (Number) The ACL rule number (0-4294967294); keep in mind that there are no sequence numbers with AS-Path lists. When you want to add a new line between you have to completely remove the ACL! + diff --git a/docs/data-sources/quagga_bgp_communitylist.md b/docs/data-sources/quagga_bgp_communitylist.md new file mode 100644 index 0000000..d0c484a --- /dev/null +++ b/docs/data-sources/quagga_bgp_communitylist.md @@ -0,0 +1,27 @@ +--- +page_title: "opnsense_quagga_bgp_communitylist Data Source - terraform-provider-opnsense" +subcategory: Quagga +description: |- + Configure community lists for BGP. +--- + +# opnsense_quagga_bgp_communitylist (Data Source) + +Configure community lists for BGP. + + +## Schema + +### Required + +- `id` (String) UUID of the resource. + +### Read-Only + +- `action` (String) Set permit for match or deny to negate the rule. +- `community` (String) The community you want to match. You can also regex and it is not validated so please be careful. +- `description` (String) An optional description for this prefix list. +- `enabled` (Boolean) Enable this community list. +- `number` (Number) Set the number of your Community-List. 1-99 are standard lists while 100-500 are expanded lists. +- `seq_number` (Number) The ACL sequence number (10-99). + diff --git a/docs/data-sources/quagga_bgp_neighbor.md b/docs/data-sources/quagga_bgp_neighbor.md new file mode 100644 index 0000000..5c58610 --- /dev/null +++ b/docs/data-sources/quagga_bgp_neighbor.md @@ -0,0 +1,47 @@ +--- +page_title: "opnsense_quagga_bgp_neighbor Data Source - terraform-provider-opnsense" +subcategory: Quagga +description: |- + Configure neighbors for BGP. +--- + +# opnsense_quagga_bgp_neighbor (Data Source) + +Configure neighbors for BGP. + + +## Schema + +### Required + +- `id` (String) UUID of the resource. + +### Read-Only + +- `as_override` (Boolean) Override AS number of the originating router with the local AS number. This command is only allowed for eBGP peers. +- `attribute_unchanged` (String) Specify attribute to be left unchanged when sending advertisements to a peer. Read more at FRR documentation. +- `bfd` (Boolean) Enable BFD support for this neighbor. +- `connect_timer` (Number) The time in seconds how fast a neighbor tries to reconnect. +- `default_route` (Boolean) Enable to send Defaultroute. +- `description` (String) An optional description for this neighbor. +- `disable_connected_check` (Boolean) Enable to allow peerings between directly connected eBGP peers using loopback addresses. +- `enabled` (Boolean) Enable this neighbor. +- `hold_down` (Number) The time in seconds when a neighbor is considered dead. This is usually 3 times the keepalive timer. +- `keep_alive` (Number) Enable Keepalive timer to check if the neighbor is still up. +- `link_local_interface` (String) Interface to use for IPv6 link-local neighbours. Must be a valid OPNsense interface in lowercase (e.g. `wan`). Please refer to the FRR documentation for more information. +- `local_ip` (String) The local IP connecting to the neighbor. This is only required for BGP authentication. +- `md5_password` (String) The password for BGP authentication. +- `multi_hop` (Boolean) Enable multi-hop. Specifying ebgp-multihop allows sessions with eBGP neighbors to establish when they are multiple hops away. When the neighbor is not directly connected and this knob is not enabled, the session will not establish. +- `multi_protocol` (Boolean) Mark this neighbor as multiprotocol capable per RFC 2283. +- `next_hop_self` (Boolean) Enable the next-hop-self command. +- `next_hop_self_all` (Boolean) Add the parameter "all" after next-hop-self command. +- `peer_ip` (String) The IP of your neighbor. +- `prefix_list_in` (String) The prefix list ID for inbound direction. +- `prefix_list_out` (String) The prefix list ID for outbound direction. +- `remote_as` (Number) The neighbor AS. +- `route_map_in` (String) The route map ID for inbound direction. +- `route_map_out` (String) The route map ID for outbound direction. +- `rr_client` (Boolean) Enable route reflector client. +- `update_source` (String) Physical name of the IPv4 interface facing the peer. Must be a valid OPNsense interface in lowercase (e.g. `wan`). Please refer to the FRR documentation for more information. +- `weight` (Number) Specify a default weight value for the neighbor’s routes. + diff --git a/docs/data-sources/quagga_bgp_prefixlist.md b/docs/data-sources/quagga_bgp_prefixlist.md new file mode 100644 index 0000000..5227d82 --- /dev/null +++ b/docs/data-sources/quagga_bgp_prefixlist.md @@ -0,0 +1,28 @@ +--- +page_title: "opnsense_quagga_bgp_prefixlist Data Source - terraform-provider-opnsense" +subcategory: Quagga +description: |- + Configure prefix lists for BGP. +--- + +# opnsense_quagga_bgp_prefixlist (Data Source) + +Configure prefix lists for BGP. + + +## Schema + +### Required + +- `id` (String) UUID of the resource. + +### Read-Only + +- `action` (String) Set permit for match or deny to negate the rule. +- `description` (String) An optional description for this prefix list. +- `enabled` (Boolean) Enable this prefix list. +- `ip_version` (String) Set the IP version to use. +- `name` (String) The name of this prefix list. +- `network` (String) The network pattern you want to match. You can also add "ge" or "le" additions after the network statement. It's not validated so please be careful! +- `number` (Number) The ACL sequence number (1-4294967294). + diff --git a/docs/data-sources/quagga_bgp_routemap.md b/docs/data-sources/quagga_bgp_routemap.md new file mode 100644 index 0000000..23461d0 --- /dev/null +++ b/docs/data-sources/quagga_bgp_routemap.md @@ -0,0 +1,30 @@ +--- +page_title: "opnsense_quagga_bgp_routemap Data Source - terraform-provider-opnsense" +subcategory: Quagga +description: |- + Configure route maps for BGP. +--- + +# opnsense_quagga_bgp_routemap (Data Source) + +Configure route maps for BGP. + + +## Schema + +### Required + +- `id` (String) UUID of the resource. + +### Read-Only + +- `action` (String) Set permit for match or deny to negate the rule. +- `aspaths` (Set of String) Set the AS Path list IDs to use. +- `community_lists` (Set of String) Set the community list IDs to use. +- `description` (String) An optional description for this route map. +- `enabled` (Boolean) Enable this route map. +- `name` (String) The name of this route map. +- `prefix_lists` (Set of String) Set the prefix list IDs to use. +- `route_map_id` (Number) The Route-map ID between 1 and 65535. Be aware that the sorting will be done under the hood, so when you add an entry between it gets to the right position. +- `set` (String) Free text field for your set, please be careful! You can set e.g. `local-preference 300` or `community 1:1` (http://www.nongnu.org/quagga/docs/docs-multi/Route-Map-Set-Command.html#Route-Map-Set-Command). Defaults to `""`. + diff --git a/docs/resources/quagga_bgp_aspath.md b/docs/resources/quagga_bgp_aspath.md new file mode 100644 index 0000000..37a97dd --- /dev/null +++ b/docs/resources/quagga_bgp_aspath.md @@ -0,0 +1,44 @@ +--- +page_title: "opnsense_quagga_bgp_aspath Resource - terraform-provider-opnsense" +subcategory: Quagga +description: |- + Configure AS Path lists for BGP. +--- + +# opnsense_quagga_bgp_aspath (Resource) + +Configure AS Path lists for BGP. + +## Example Usage + +```terraform +// Configure an AS Path +resource "opnsense_quagga_bgp_aspath" "example0" { + enabled = false + description = "aspath0" + + number = 123 + action = "permit" + + as = "_2$" +} +``` + + +## Schema + +### Required + +- `as` (String) The AS pattern you want to match, regexp allowed (e.g. `.$` or `_1$`). It's not validated so please be careful! +- `number` (Number) The ACL rule number (0-4294967294); keep in mind that there are no sequence numbers with AS-Path lists. When you want to add a new line between you have to completely remove the ACL! + +### Optional + +- `action` (String) Set permit for match or deny to negate the rule. Defaults to `"permit"`. +- `description` (String) An optional description for this AS path. Defaults to `""`. +- `enabled` (Boolean) Enable this AS path. Defaults to `true`. + +### Read-Only + +- `id` (String) UUID of the AS path. + diff --git a/docs/resources/quagga_bgp_communitylist.md b/docs/resources/quagga_bgp_communitylist.md new file mode 100644 index 0000000..ff75b11 --- /dev/null +++ b/docs/resources/quagga_bgp_communitylist.md @@ -0,0 +1,46 @@ +--- +page_title: "opnsense_quagga_bgp_communitylist Resource - terraform-provider-opnsense" +subcategory: Quagga +description: |- + Configure community lists for BGP. +--- + +# opnsense_quagga_bgp_communitylist (Resource) + +Configure community lists for BGP. + +## Example Usage + +```terraform +// Configure a community list +resource "opnsense_quagga_bgp_communitylist" "example0" { + enabled = false + description = "communitylist0" + + number = 100 + seq_number = 99 + action = "deny" + + community = "example.*" +} +``` + + +## Schema + +### Required + +- `community` (String) The community you want to match. You can also regex and it is not validated so please be careful. +- `number` (Number) Set the number of your Community-List. 1-99 are standard lists while 100-500 are expanded lists. +- `seq_number` (Number) The ACL sequence number (10-99). + +### Optional + +- `action` (String) Set permit for match or deny to negate the rule. Defaults to `"permit"`. +- `description` (String) An optional description for this prefix list. Defaults to `""`. +- `enabled` (Boolean) Enable this community list. Defaults to `true`. + +### Read-Only + +- `id` (String) UUID of the community list. + diff --git a/docs/resources/quagga_bgp_neighbor.md b/docs/resources/quagga_bgp_neighbor.md new file mode 100644 index 0000000..f13346c --- /dev/null +++ b/docs/resources/quagga_bgp_neighbor.md @@ -0,0 +1,114 @@ +--- +page_title: "opnsense_quagga_bgp_neighbor Resource - terraform-provider-opnsense" +subcategory: Quagga +description: |- + Configure neighbors for BGP. +--- + +# opnsense_quagga_bgp_neighbor (Resource) + +Configure neighbors for BGP. + +## Example Usage + +```terraform +// Configure a prefix list +resource "opnsense_quagga_bgp_prefixlist" "example0" { + enabled = false + + description = "prefixlist0" + name = "example0" + + number = 1234 + action = "permit" + + network = "10.10.0.0" +} + +// Configure a route map +resource "opnsense_quagga_bgp_routemap" "example0" { + enabled = false + description = "routemap0" + + name = "example0" + action = "deny" + + route_map_id = 100 + set = "local-preference 300" +} + +// Configure a neighbor +resource "opnsense_quagga_bgp_neighbor" "example0" { + enabled = false + + description = "neighbor0" + + peer_ip = "1.1.1.1" + remote_as = 255 + + md5_password = "12345" + weight = 1 + local_ip = "2.2.2.2" + update_source = "wan" + link_local_interface = "wireguard" + + next_hop_self = true + next_hop_self_all = true + multi_hop = true + multi_protocol = true + rr_client = true + bfd = true + + keep_alive = 100 + hold_down = 10 + connect_timer = 10 + + default_route = true + as_override = true + disable_connected_check = true + attribute_unchanged = "as-path" + + prefix_list_in = opnsense_quagga_bgp_prefixlist.example0.id + route_map_out = opnsense_quagga_bgp_routemap.example0.id +} +``` + + +## Schema + +### Required + +- `peer_ip` (String) The IP of your neighbor. +- `remote_as` (Number) The neighbor AS. + +### Optional + +- `as_override` (Boolean) Override AS number of the originating router with the local AS number. This command is only allowed for eBGP peers. Defaults to `false`. +- `attribute_unchanged` (String) Specify attribute to be left unchanged when sending advertisements to a peer. Read more at FRR documentation. Defaults to `""`. +- `bfd` (Boolean) Enable BFD support for this neighbor. Defaults to `false`. +- `connect_timer` (Number) The time in seconds how fast a neighbor tries to reconnect. Defaults to `-1`. +- `default_route` (Boolean) Enable to send Defaultroute. Defaults to `false`. +- `description` (String) An optional description for this neighbor. Defaults to `""`. +- `disable_connected_check` (Boolean) Enable to allow peerings between directly connected eBGP peers using loopback addresses. Defaults to `false`. +- `enabled` (Boolean) Enable this neighbor. Defaults to `true`. +- `hold_down` (Number) The time in seconds when a neighbor is considered dead. This is usually 3 times the keepalive timer. Defaults to `180`. +- `keep_alive` (Number) Enable Keepalive timer to check if the neighbor is still up. Defaults to `60`. +- `link_local_interface` (String) Interface to use for IPv6 link-local neighbours. Must be a valid OPNsense interface in lowercase (e.g. `wan`). Please refer to the FRR documentation for more information. Defaults to `""`. +- `local_ip` (String) The local IP connecting to the neighbor. This is only required for BGP authentication. Defaults to `""`. +- `md5_password` (String) The password for BGP authentication. Defaults to `""`. +- `multi_hop` (Boolean) Enable multi-hop. Specifying ebgp-multihop allows sessions with eBGP neighbors to establish when they are multiple hops away. When the neighbor is not directly connected and this knob is not enabled, the session will not establish. Defaults to `false`. +- `multi_protocol` (Boolean) Mark this neighbor as multiprotocol capable per RFC 2283. Defaults to `false`. +- `next_hop_self` (Boolean) Enable the next-hop-self command. Defaults to `false`. +- `next_hop_self_all` (Boolean) Add the parameter "all" after next-hop-self command. Defaults to `false`. +- `prefix_list_in` (String) The prefix list ID for inbound direction. Defaults to `""`. +- `prefix_list_out` (String) The prefix list ID for outbound direction. Defaults to `""`. +- `route_map_in` (String) The route map ID for inbound direction. Defaults to `""`. +- `route_map_out` (String) The route map ID for outbound direction. Defaults to `""`. +- `rr_client` (Boolean) Enable route reflector client. Defaults to `false`. +- `update_source` (String) Physical name of the IPv4 interface facing the peer. Must be a valid OPNsense interface in lowercase (e.g. `wan`). Please refer to the FRR documentation for more information. Defaults to `""`. +- `weight` (Number) Specify a default weight value for the neighbor’s routes. Defaults to `-1`. + +### Read-Only + +- `id` (String) UUID of the neighbor. + diff --git a/docs/resources/quagga_bgp_prefixlist.md b/docs/resources/quagga_bgp_prefixlist.md new file mode 100644 index 0000000..8dc0eba --- /dev/null +++ b/docs/resources/quagga_bgp_prefixlist.md @@ -0,0 +1,48 @@ +--- +page_title: "opnsense_quagga_bgp_prefixlist Resource - terraform-provider-opnsense" +subcategory: Quagga +description: |- + Configure prefix lists for BGP. +--- + +# opnsense_quagga_bgp_prefixlist (Resource) + +Configure prefix lists for BGP. + +## Example Usage + +```terraform +// Configure a prefix list +resource "opnsense_quagga_bgp_prefixlist" "example0" { + enabled = false + + description = "prefixlist0" + name = "example0" + + number = 1234 + action = "permit" + + network = "10.10.0.0" +} +``` + + +## Schema + +### Required + +- `name` (String) The name of this prefix list. +- `network` (String) The network pattern you want to match. You can also add "ge" or "le" additions after the network statement. It's not validated so please be careful! +- `number` (Number) The ACL sequence number (1-4294967294). + +### Optional + +- `action` (String) Set permit for match or deny to negate the rule. Defaults to `"permit"`. +- `description` (String) An optional description for this prefix list. Defaults to `""`. +- `enabled` (Boolean) Enable this prefix list. Defaults to `true`. +- `ip_version` (String) Set the IP version to use. Defaults to `"IPv4"`. + +### Read-Only + +- `id` (String) UUID of the prefix list. + diff --git a/docs/resources/quagga_bgp_routemap.md b/docs/resources/quagga_bgp_routemap.md new file mode 100644 index 0000000..b653286 --- /dev/null +++ b/docs/resources/quagga_bgp_routemap.md @@ -0,0 +1,97 @@ +--- +page_title: "opnsense_quagga_bgp_routemap Resource - terraform-provider-opnsense" +subcategory: Quagga +description: |- + Configure route maps for BGP. +--- + +# opnsense_quagga_bgp_routemap (Resource) + +Configure route maps for BGP. + +## Example Usage + +```terraform +// Configure an AS Path +resource "opnsense_quagga_bgp_aspath" "example0" { + enabled = false + description = "aspath0" + + number = 123 + action = "permit" + + as = "_2$" +} + +// Configure a prefix list +resource "opnsense_quagga_bgp_prefixlist" "example0" { + enabled = false + + description = "prefixlist0" + name = "example0" + + number = 1234 + action = "permit" + + network = "10.10.0.0" +} +// Configure a community list +resource "opnsense_quagga_bgp_communitylist" "example0" { + enabled = false + description = "communitylist0" + + number = 100 + seq_number = 99 + action = "deny" + + community = "example.*" +} + +// Configure a route map +resource "opnsense_quagga_bgp_routemap" "example0" { + enabled = false + description = "routemap0" + + name = "example0" + action = "deny" + + route_map_id = 100 + + aspaths = [ + opnsense_quagga_bgp_aspath.example0.id + ] + + prefix_lists = [ + opnsense_quagga_bgp_prefixlist.example0.id + ] + + community_lists = [ + opnsense_quagga_bgp_communitylist.example0.id + ] + + set = "local-preference 300" +} +``` + + +## Schema + +### Required + +- `name` (String) The name of this route map. +- `route_map_id` (Number) The Route-map ID between 1 and 65535. Be aware that the sorting will be done under the hood, so when you add an entry between it gets to the right position. + +### Optional + +- `action` (String) Set permit for match or deny to negate the rule. Defaults to `"permit"`. +- `aspaths` (Set of String) Set the AS Path list IDs to use. Defaults to `[]`. +- `community_lists` (Set of String) Set the community list IDs to use. Defaults to `[]`. +- `description` (String) An optional description for this route map. Defaults to `""`. +- `enabled` (Boolean) Enable this route map. Defaults to `true`. +- `prefix_lists` (Set of String) Set the prefix list IDs to use. Defaults to `[]`. +- `set` (String) Free text field for your set, please be careful! You can set e.g. `local-preference 300` or `community 1:1` (http://www.nongnu.org/quagga/docs/docs-multi/Route-Map-Set-Command.html#Route-Map-Set-Command). Defaults to `""`. + +### Read-Only + +- `id` (String) UUID of the route map. + diff --git a/examples/resources/opnsense_quagga_bgp_aspath/resource.tf b/examples/resources/opnsense_quagga_bgp_aspath/resource.tf new file mode 100644 index 0000000..adc6849 --- /dev/null +++ b/examples/resources/opnsense_quagga_bgp_aspath/resource.tf @@ -0,0 +1,10 @@ +// Configure an AS Path +resource "opnsense_quagga_bgp_aspath" "example0" { + enabled = false + description = "aspath0" + + number = 123 + action = "permit" + + as = "_2$" +} diff --git a/examples/resources/opnsense_quagga_bgp_communitylist/resource.tf b/examples/resources/opnsense_quagga_bgp_communitylist/resource.tf new file mode 100644 index 0000000..59d320d --- /dev/null +++ b/examples/resources/opnsense_quagga_bgp_communitylist/resource.tf @@ -0,0 +1,11 @@ +// Configure a community list +resource "opnsense_quagga_bgp_communitylist" "example0" { + enabled = false + description = "communitylist0" + + number = 100 + seq_number = 99 + action = "deny" + + community = "example.*" +} diff --git a/examples/resources/opnsense_quagga_bgp_neighbor/resource.tf b/examples/resources/opnsense_quagga_bgp_neighbor/resource.tf new file mode 100644 index 0000000..19d407c --- /dev/null +++ b/examples/resources/opnsense_quagga_bgp_neighbor/resource.tf @@ -0,0 +1,59 @@ +// Configure a prefix list +resource "opnsense_quagga_bgp_prefixlist" "example0" { + enabled = false + + description = "prefixlist0" + name = "example0" + + number = 1234 + action = "permit" + + network = "10.10.0.0" +} + +// Configure a route map +resource "opnsense_quagga_bgp_routemap" "example0" { + enabled = false + description = "routemap0" + + name = "example0" + action = "deny" + + route_map_id = 100 + set = "local-preference 300" +} + +// Configure a neighbor +resource "opnsense_quagga_bgp_neighbor" "example0" { + enabled = false + + description = "neighbor0" + + peer_ip = "1.1.1.1" + remote_as = 255 + + md5_password = "12345" + weight = 1 + local_ip = "2.2.2.2" + update_source = "wan" + link_local_interface = "wireguard" + + next_hop_self = true + next_hop_self_all = true + multi_hop = true + multi_protocol = true + rr_client = true + bfd = true + + keep_alive = 100 + hold_down = 10 + connect_timer = 10 + + default_route = true + as_override = true + disable_connected_check = true + attribute_unchanged = "as-path" + + prefix_list_in = opnsense_quagga_bgp_prefixlist.example0.id + route_map_out = opnsense_quagga_bgp_routemap.example0.id +} diff --git a/examples/resources/opnsense_quagga_bgp_prefixlist/resource.tf b/examples/resources/opnsense_quagga_bgp_prefixlist/resource.tf new file mode 100644 index 0000000..b58dd92 --- /dev/null +++ b/examples/resources/opnsense_quagga_bgp_prefixlist/resource.tf @@ -0,0 +1,12 @@ +// Configure a prefix list +resource "opnsense_quagga_bgp_prefixlist" "example0" { + enabled = false + + description = "prefixlist0" + name = "example0" + + number = 1234 + action = "permit" + + network = "10.10.0.0" +} diff --git a/examples/resources/opnsense_quagga_bgp_routemap/resource.tf b/examples/resources/opnsense_quagga_bgp_routemap/resource.tf new file mode 100644 index 0000000..ec04c3a --- /dev/null +++ b/examples/resources/opnsense_quagga_bgp_routemap/resource.tf @@ -0,0 +1,59 @@ +// Configure an AS Path +resource "opnsense_quagga_bgp_aspath" "example0" { + enabled = false + description = "aspath0" + + number = 123 + action = "permit" + + as = "_2$" +} + +// Configure a prefix list +resource "opnsense_quagga_bgp_prefixlist" "example0" { + enabled = false + + description = "prefixlist0" + name = "example0" + + number = 1234 + action = "permit" + + network = "10.10.0.0" +} +// Configure a community list +resource "opnsense_quagga_bgp_communitylist" "example0" { + enabled = false + description = "communitylist0" + + number = 100 + seq_number = 99 + action = "deny" + + community = "example.*" +} + +// Configure a route map +resource "opnsense_quagga_bgp_routemap" "example0" { + enabled = false + description = "routemap0" + + name = "example0" + action = "deny" + + route_map_id = 100 + + aspaths = [ + opnsense_quagga_bgp_aspath.example0.id + ] + + prefix_lists = [ + opnsense_quagga_bgp_prefixlist.example0.id + ] + + community_lists = [ + opnsense_quagga_bgp_communitylist.example0.id + ] + + set = "local-preference 300" +} diff --git a/templates/data-sources/quagga_bgp_aspath.md.tmpl b/templates/data-sources/quagga_bgp_aspath.md.tmpl new file mode 100644 index 0000000..8334fbb --- /dev/null +++ b/templates/data-sources/quagga_bgp_aspath.md.tmpl @@ -0,0 +1,20 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.RenderedProviderName}}" +subcategory: Quagga +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +{{ .SchemaMarkdown | trimspace }} + +{{ if .HasImport -}} +## Import + +Import is supported using the following syntax: + +{{ printf "{{codefile \"shell\" %q}}" .ImportFile }} +{{- end }} \ No newline at end of file diff --git a/templates/data-sources/quagga_bgp_communitylist.md.tmpl b/templates/data-sources/quagga_bgp_communitylist.md.tmpl new file mode 100644 index 0000000..8334fbb --- /dev/null +++ b/templates/data-sources/quagga_bgp_communitylist.md.tmpl @@ -0,0 +1,20 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.RenderedProviderName}}" +subcategory: Quagga +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +{{ .SchemaMarkdown | trimspace }} + +{{ if .HasImport -}} +## Import + +Import is supported using the following syntax: + +{{ printf "{{codefile \"shell\" %q}}" .ImportFile }} +{{- end }} \ No newline at end of file diff --git a/templates/data-sources/quagga_bgp_neighbor.md.tmpl b/templates/data-sources/quagga_bgp_neighbor.md.tmpl new file mode 100644 index 0000000..8334fbb --- /dev/null +++ b/templates/data-sources/quagga_bgp_neighbor.md.tmpl @@ -0,0 +1,20 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.RenderedProviderName}}" +subcategory: Quagga +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +{{ .SchemaMarkdown | trimspace }} + +{{ if .HasImport -}} +## Import + +Import is supported using the following syntax: + +{{ printf "{{codefile \"shell\" %q}}" .ImportFile }} +{{- end }} \ No newline at end of file diff --git a/templates/data-sources/quagga_bgp_prefixlist.md.tmpl b/templates/data-sources/quagga_bgp_prefixlist.md.tmpl new file mode 100644 index 0000000..8334fbb --- /dev/null +++ b/templates/data-sources/quagga_bgp_prefixlist.md.tmpl @@ -0,0 +1,20 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.RenderedProviderName}}" +subcategory: Quagga +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +{{ .SchemaMarkdown | trimspace }} + +{{ if .HasImport -}} +## Import + +Import is supported using the following syntax: + +{{ printf "{{codefile \"shell\" %q}}" .ImportFile }} +{{- end }} \ No newline at end of file diff --git a/templates/data-sources/quagga_bgp_routemap.md.tmpl b/templates/data-sources/quagga_bgp_routemap.md.tmpl new file mode 100644 index 0000000..8334fbb --- /dev/null +++ b/templates/data-sources/quagga_bgp_routemap.md.tmpl @@ -0,0 +1,20 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.RenderedProviderName}}" +subcategory: Quagga +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +{{ .SchemaMarkdown | trimspace }} + +{{ if .HasImport -}} +## Import + +Import is supported using the following syntax: + +{{ printf "{{codefile \"shell\" %q}}" .ImportFile }} +{{- end }} \ No newline at end of file diff --git a/templates/resources/quagga_bgp_aspath.md.tmpl b/templates/resources/quagga_bgp_aspath.md.tmpl new file mode 100644 index 0000000..4746958 --- /dev/null +++ b/templates/resources/quagga_bgp_aspath.md.tmpl @@ -0,0 +1,24 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.RenderedProviderName}}" +subcategory: Quagga +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +## Example Usage + +{{ tffile (printf "%s%s%s" "examples/resources/" .Name "/resource.tf") }} + +{{ .SchemaMarkdown | trimspace }} + +{{ if .HasImport -}} +## Import + +Import is supported using the following syntax: + +{{ printf "{{codefile \"shell\" %q}}" .ImportFile }} +{{- end }} \ No newline at end of file diff --git a/templates/resources/quagga_bgp_communitylist.md.tmpl b/templates/resources/quagga_bgp_communitylist.md.tmpl new file mode 100644 index 0000000..4746958 --- /dev/null +++ b/templates/resources/quagga_bgp_communitylist.md.tmpl @@ -0,0 +1,24 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.RenderedProviderName}}" +subcategory: Quagga +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +## Example Usage + +{{ tffile (printf "%s%s%s" "examples/resources/" .Name "/resource.tf") }} + +{{ .SchemaMarkdown | trimspace }} + +{{ if .HasImport -}} +## Import + +Import is supported using the following syntax: + +{{ printf "{{codefile \"shell\" %q}}" .ImportFile }} +{{- end }} \ No newline at end of file diff --git a/templates/resources/quagga_bgp_neighbor.md.tmpl b/templates/resources/quagga_bgp_neighbor.md.tmpl new file mode 100644 index 0000000..4746958 --- /dev/null +++ b/templates/resources/quagga_bgp_neighbor.md.tmpl @@ -0,0 +1,24 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.RenderedProviderName}}" +subcategory: Quagga +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +## Example Usage + +{{ tffile (printf "%s%s%s" "examples/resources/" .Name "/resource.tf") }} + +{{ .SchemaMarkdown | trimspace }} + +{{ if .HasImport -}} +## Import + +Import is supported using the following syntax: + +{{ printf "{{codefile \"shell\" %q}}" .ImportFile }} +{{- end }} \ No newline at end of file diff --git a/templates/resources/quagga_bgp_prefixlist.md.tmpl b/templates/resources/quagga_bgp_prefixlist.md.tmpl new file mode 100644 index 0000000..4746958 --- /dev/null +++ b/templates/resources/quagga_bgp_prefixlist.md.tmpl @@ -0,0 +1,24 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.RenderedProviderName}}" +subcategory: Quagga +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +## Example Usage + +{{ tffile (printf "%s%s%s" "examples/resources/" .Name "/resource.tf") }} + +{{ .SchemaMarkdown | trimspace }} + +{{ if .HasImport -}} +## Import + +Import is supported using the following syntax: + +{{ printf "{{codefile \"shell\" %q}}" .ImportFile }} +{{- end }} \ No newline at end of file diff --git a/templates/resources/quagga_bgp_routemap.md.tmpl b/templates/resources/quagga_bgp_routemap.md.tmpl new file mode 100644 index 0000000..4746958 --- /dev/null +++ b/templates/resources/quagga_bgp_routemap.md.tmpl @@ -0,0 +1,24 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.RenderedProviderName}}" +subcategory: Quagga +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +## Example Usage + +{{ tffile (printf "%s%s%s" "examples/resources/" .Name "/resource.tf") }} + +{{ .SchemaMarkdown | trimspace }} + +{{ if .HasImport -}} +## Import + +Import is supported using the following syntax: + +{{ printf "{{codefile \"shell\" %q}}" .ImportFile }} +{{- end }} \ No newline at end of file