diff --git a/client/bgp_instance.go b/client/bgp_instance.go index 7fd752a..d71fe7a 100644 --- a/client/bgp_instance.go +++ b/client/bgp_instance.go @@ -23,24 +23,24 @@ func legacyBgpUnsupported(err error) bool { // BgpInstance Mikrotik resource type BgpInstance struct { - Id string `mikrotik:".id"` - Name string `mikrotik:"name"` - As int `mikrotik:"as"` - ClientToClientReflection bool `mikrotik:"client-to-client-reflection"` - Comment string `mikrotik:"comment"` - ConfederationPeers string `mikrotik:"confederation-peers"` - Disabled bool `mikrotik:"disabled"` - IgnoreAsPathLen bool `mikrotik:"ignore-as-path-len"` - OutFilter string `mikrotik:"out-filter"` - RedistributeConnected bool `mikrotik:"redistribute-connected"` - RedistributeOspf bool `mikrotik:"redistribute-ospf"` - RedistributeOtherBgp bool `mikrotik:"redistribute-other-bgp"` - RedistributeRip bool `mikrotik:"redistribute-rip"` - RedistributeStatic bool `mikrotik:"redistribute-static"` - RouterID string `mikrotik:"router-id"` - RoutingTable string `mikrotik:"routing-table"` - ClusterID string `mikrotik:"cluster-id"` - Confederation int `mikrotik:"confederation"` + Id string `mikrotik:".id" codegen:"id,mikrotikID"` + Name string `mikrotik:"name" codegen:"name,required,terraformID"` + As int `mikrotik:"as" codegen:"as,required"` + ClientToClientReflection bool `mikrotik:"client-to-client-reflection" codegen:"client_to_client_reflection"` + Comment string `mikrotik:"comment" codegen:"comment"` + ConfederationPeers string `mikrotik:"confederation-peers" codegen:"confederation_peers"` + Disabled bool `mikrotik:"disabled" codegen:"disabled"` + IgnoreAsPathLen bool `mikrotik:"ignore-as-path-len" codegen:"ignore_as_path_len"` + OutFilter string `mikrotik:"out-filter" codegen:"out_filter"` + RedistributeConnected bool `mikrotik:"redistribute-connected" codegen:"redistribute_connected"` + RedistributeOspf bool `mikrotik:"redistribute-ospf" codegen:"redistribute_ospf"` + RedistributeOtherBgp bool `mikrotik:"redistribute-other-bgp" codegen:"redistribute_other_bgp"` + RedistributeRip bool `mikrotik:"redistribute-rip" codegen:"redistribute_rip"` + RedistributeStatic bool `mikrotik:"redistribute-static" codegen:"redistribute_static"` + RouterID string `mikrotik:"router-id" codegen:"router_id,required"` + RoutingTable string `mikrotik:"routing-table" codegen:"routing_table"` + ClusterID string `mikrotik:"cluster-id" codegen:"cluster_id"` + Confederation int `mikrotik:"confederation" codegen:"confederation"` } var _ Resource = (*BgpInstance)(nil) @@ -86,12 +86,19 @@ func (b *BgpInstance) DeleteFieldValue() string { return b.Name } +// HandleError intercepts errors during CRUD operations. +// It is used to catch "no such command prefix" on RouterOS >= v7.0 +func (b *BgpInstance) HandleError(err error) error { + if legacyBgpUnsupported(err) { + return LegacyBgpUnsupported{} + } + + return err +} + // Typed wrappers func (c Mikrotik) AddBgpInstance(r *BgpInstance) (*BgpInstance, error) { res, err := c.Add(r) - if legacyBgpUnsupported(err) { - return nil, LegacyBgpUnsupported{} - } if err != nil { return nil, err } @@ -101,9 +108,6 @@ func (c Mikrotik) AddBgpInstance(r *BgpInstance) (*BgpInstance, error) { func (c Mikrotik) UpdateBgpInstance(r *BgpInstance) (*BgpInstance, error) { res, err := c.Update(r) - if legacyBgpUnsupported(err) { - return nil, LegacyBgpUnsupported{} - } if err != nil { return nil, err } @@ -113,10 +117,6 @@ func (c Mikrotik) UpdateBgpInstance(r *BgpInstance) (*BgpInstance, error) { func (c Mikrotik) FindBgpInstance(name string) (*BgpInstance, error) { res, err := c.Find(&BgpInstance{Name: name}) - if legacyBgpUnsupported(err) { - return nil, LegacyBgpUnsupported{} - } - if err != nil { return nil, err } @@ -126,9 +126,5 @@ func (c Mikrotik) FindBgpInstance(name string) (*BgpInstance, error) { func (c Mikrotik) DeleteBgpInstance(name string) error { err := c.Delete(&BgpInstance{Name: name}) - if legacyBgpUnsupported(err) { - return LegacyBgpUnsupported{} - } - return err } diff --git a/client/client_crud.go b/client/client_crud.go index d618e4d..2eeb8de 100644 --- a/client/client_crud.go +++ b/client/client_crud.go @@ -17,49 +17,55 @@ const ( ) type ( - // Action represents possible action on resource + // Action represents possible action on resource. Action string - // Resource interface defines a contract for abstract RouterOS resource + // Resource interface defines a contract for abstract RouterOS resource. Resource interface { - // ActionToCommand translates CRUD action to RouterOS command path + // ActionToCommand translates CRUD action to RouterOS command path. ActionToCommand(Action) string - // IDField reveals name of ID field to use in requests to MikroTik router - // It is used in operations like Find + // IDField reveals name of ID field to use in requests to MikroTik router. + // It is used in operations like Find. IDField() string - // ID returns value of the ID field + // ID returns value of the ID field. ID() string - // SetID updates a value of the ID field + // SetID updates a value of the ID field. SetID(string) } - // Adder defines contract for resources which require custom behaviour during resource creation + // Adder defines contract for resources which require custom behaviour during resource creation. Adder interface { - // AfterAddHook is called right after the resource successfully added - // This hook is mainly used to set resource's ID field based on reply from RouterOS + // AfterAddHook is called right after the resource successfully added. + // This hook is mainly used to set resource's ID field based on reply from RouterOS. AfterAddHook(r *routeros.Reply) } - // Finder defines contract for resources which provide custom behaviour during resource retrieval + // Finder defines contract for resources which provide custom behaviour during resource retrieval. Finder interface { - // FindField retrieves a name of a field to use as key for resource retrieval + // FindField retrieves a name of a field to use as key for resource retrieval. FindField() string - // FindFieldValue retrieves a value to use for resource retrieval + // FindFieldValue retrieves a value to use for resource retrieval. FindFieldValue() string } - // Deleter defines contract for resources which require custom behaviour during resource deletion + // Deleter defines contract for resources which require custom behaviour during resource deletion. Deleter interface { - // DeleteField retrieves a name of a field which is used for resource deletion + // DeleteField retrieves a name of a field which is used for resource deletion. DeleteField() string - // DeleteFieldValue retrieves a value for DeleteField field + // DeleteFieldValue retrieves a value for DeleteField field. DeleteFieldValue() string } + + // ErrorHandler Defines contract to handle errors returned by RouterOS. + // It can either return another error, or supress original error by returning nil. + ErrorHandler interface { + HandleError(error) error + } ) // Add creates new resource on remote system @@ -72,6 +78,9 @@ func (client Mikrotik) Add(d Resource) (Resource, error) { cmd := Marshal(d.ActionToCommand(Add), d) log.Printf("[INFO] Running the mikrotik command: `%s`", cmd) r, err := c.RunArgs(cmd) + if eh, ok := d.(ErrorHandler); ok { + err = eh.HandleError(err) + } if err != nil { return nil, err } @@ -93,6 +102,9 @@ func (client Mikrotik) List(d Resource) ([]Resource, error) { return nil, err } r, err := c.RunArgs(cmd) + if eh, ok := d.(ErrorHandler); ok { + err = eh.HandleError(err) + } if err != nil { return nil, err } @@ -135,7 +147,9 @@ func (client Mikrotik) Update(resource Resource) (Resource, error) { cmd := Marshal(resource.ActionToCommand(Update), resource) log.Printf("[INFO] Running the mikrotik command: `%s`", cmd) _, err = c.RunArgs(cmd) - + if eh, ok := resource.(ErrorHandler); ok { + err = eh.HandleError(err) + } if err != nil { return nil, err } @@ -159,6 +173,9 @@ func (client Mikrotik) Delete(d Resource) error { cmd := []string{d.ActionToCommand(Delete), "=" + deleteField + "=" + deleteFieldValue} log.Printf("[INFO] Running the mikrotik command: `%s`", cmd) _, err = c.RunArgs(cmd) + if eh, ok := d.(ErrorHandler); ok { + err = eh.HandleError(err) + } return err } @@ -172,6 +189,9 @@ func (client Mikrotik) findByField(d Resource, field, value string) (Resource, e return nil, err } r, err := c.RunArgs(cmd) + if eh, ok := d.(ErrorHandler); ok { + err = eh.HandleError(err) + } if err != nil { return nil, err } @@ -180,6 +200,9 @@ func (client Mikrotik) findByField(d Resource, field, value string) (Resource, e targetStruct := client.newTargetStruct(d) targetStructInterface := targetStruct.Interface() err = Unmarshal(*r, targetStructInterface) + if eh, ok := d.(ErrorHandler); ok { + err = eh.HandleError(err) + } if err != nil { return nil, err } diff --git a/docs/resources/bgp_instance.md b/docs/resources/bgp_instance.md index ca45340..dd88e68 100644 --- a/docs/resources/bgp_instance.md +++ b/docs/resources/bgp_instance.md @@ -26,24 +26,24 @@ resource "mikrotik_bgp_instance" "instance" { ### Optional -- `client_to_client_reflection` (Boolean) The comment of the IP Pool to be created. Default: `true`. +- `client_to_client_reflection` (Boolean) In case this instance is a route reflector: whether to redistribute routes learned from one routing reflection client to other clients. - `cluster_id` (String) In case this instance is a route reflector: cluster ID of the router reflector cluster this instance belongs to. - `comment` (String) The comment of the BGP instance to be created. - `confederation` (Number) In case of BGP confederations: autonomous system number that identifies the [local] confederation as a whole. - `confederation_peers` (String) List of AS numbers internal to the [local] confederation. For example: `10,20,30-50`. - `disabled` (Boolean) Whether instance is disabled. -- `ignore_as_path_len` (Boolean) Whether to ignore AS_PATH attribute in BGP route selection algorithm. Default: `false`. -- `out_filter` (String) Output routing filter chain used by all BGP peers belonging to this instance. Default: `""`. -- `redistribute_connected` (Boolean) If enabled, this BGP instance will redistribute the information about connected routes. Default: `false`. -- `redistribute_ospf` (Boolean) If enabled, this BGP instance will redistribute the information about routes learned by OSPF. Default: `false`. -- `redistribute_other_bgp` (Boolean) If enabled, this BGP instance will redistribute the information about routes learned by other BGP instances. Default: `false`. -- `redistribute_rip` (Boolean) If enabled, this BGP instance will redistribute the information about routes learned by RIP. Default: `false`. -- `redistribute_static` (Boolean) If enabled, the router will redistribute the information about static routes added to its routing database. Default: `false`. -- `routing_table` (String) Name of routing table this BGP instance operates on. Default: `""`. +- `ignore_as_path_len` (Boolean) Whether to ignore AS_PATH attribute in BGP route selection algorithm. +- `out_filter` (String) Output routing filter chain used by all BGP peers belonging to this instance. +- `redistribute_connected` (Boolean) If enabled, this BGP instance will redistribute the information about connected routes. +- `redistribute_ospf` (Boolean) If enabled, this BGP instance will redistribute the information about routes learned by OSPF. +- `redistribute_other_bgp` (Boolean) If enabled, this BGP instance will redistribute the information about routes learned by other BGP instances. +- `redistribute_rip` (Boolean) If enabled, this BGP instance will redistribute the information about routes learned by RIP. +- `redistribute_static` (Boolean) If enabled, the router will redistribute the information about static routes added to its routing database. +- `routing_table` (String) Name of routing table this BGP instance operates on. ### Read-Only -- `id` (String) The ID of this resource. +- `id` (String) ID of this resource. ## Import Import is supported using the following syntax: diff --git a/mikrotik/provider.go b/mikrotik/provider.go index 70377d5..92cb677 100644 --- a/mikrotik/provider.go +++ b/mikrotik/provider.go @@ -63,9 +63,7 @@ func Provider(client *mt.Mikrotik) *schema.Provider { Description: "Insecure connection does not verify MikroTik's TLS certificate", }, }, - ResourcesMap: map[string]*schema.Resource{ - "mikrotik_bgp_instance": resourceBgpInstance(), - }, + ResourcesMap: map[string]*schema.Resource{}, } provider.ConfigureContextFunc = func(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) { diff --git a/mikrotik/provider_framework.go b/mikrotik/provider_framework.go index 727c621..8207cfa 100644 --- a/mikrotik/provider_framework.go +++ b/mikrotik/provider_framework.go @@ -182,6 +182,7 @@ func (p *ProviderFramework) DataSources(ctx context.Context) []func() datasource func (p *ProviderFramework) Resources(ctx context.Context) []func() resource.Resource { return []func() resource.Resource{ + NewBgpInstanceResource, NewBgpPeerResource, NewBridgePortResource, NewBridgeResource, diff --git a/mikrotik/provider_test.go b/mikrotik/provider_test.go index a56f254..6d4f202 100644 --- a/mikrotik/provider_test.go +++ b/mikrotik/provider_test.go @@ -2,7 +2,6 @@ package mikrotik import ( "context" - "fmt" "os" "testing" @@ -11,10 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-go/tfprotov5" "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" "github.com/hashicorp/terraform-plugin-mux/tf6to5server" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) const ( @@ -61,37 +57,3 @@ func testAccPreCheck(t *testing.T) { t.Fatal("The MIKROTIK_PASSWORD environment variable must be set") } } - -func testAccDeleteResource(resource *schema.Resource, d *schema.ResourceData, meta interface{}) error { - if resource.DeleteContext != nil { - var diags diag.Diagnostics - - diags = resource.DeleteContext(context.Background(), d, meta) - - for i := range diags { - if diags[i].Severity == diag.Error { - return fmt.Errorf("error deleting resource: %s", diags[i].Summary) - } - } - - return nil - } - - return resource.Delete(d, meta) -} - -func testAccCheckResourceDisappears(provider *schema.Provider, resource *schema.Resource, resourceName string) resource.TestCheckFunc { - return func(s *terraform.State) error { - resourceState, ok := s.RootModule().Resources[resourceName] - - if !ok { - return fmt.Errorf("resource not found: %s", resourceName) - } - - if resourceState.Primary.ID == "" { - return fmt.Errorf("resource ID missing: %s", resourceName) - } - - return testAccDeleteResource(resource, resource.Data(resourceState.Primary), provider.Meta()) - } -} diff --git a/mikrotik/resource_bgp_instance.go b/mikrotik/resource_bgp_instance.go index f60a744..5add1b5 100644 --- a/mikrotik/resource_bgp_instance.go +++ b/mikrotik/resource_bgp_instance.go @@ -4,242 +4,210 @@ import ( "context" "github.com/ddelnano/terraform-provider-mikrotik/client" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "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" + + tftypes "github.com/hashicorp/terraform-plugin-framework/types" ) -func resourceBgpInstance() *schema.Resource { - return &schema.Resource{ - Description: "Creates a Mikrotik BGP Instance.", +type bgpInstance struct { + client *client.Mikrotik +} - CreateContext: resourceBgpInstanceCreate, - ReadContext: resourceBgpInstanceRead, - UpdateContext: resourceBgpInstanceUpdate, - DeleteContext: resourceBgpInstanceDelete, - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, +// Ensure the implementation satisfies the expected interfaces. +var ( + _ resource.Resource = &bgpInstance{} + _ resource.ResourceWithConfigure = &bgpInstance{} + _ resource.ResourceWithImportState = &bgpInstance{} +) + +// NewBgpInstanceResource is a helper function to simplify the provider implementation. +func NewBgpInstanceResource() resource.Resource { + return &bgpInstance{} +} + +func (r *bgpInstance) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + r.client = req.ProviderData.(*client.Mikrotik) +} + +// Metadata returns the resource type name. +func (r *bgpInstance) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_bgp_instance" +} - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, +// Schema defines the schema for the resource. +func (s *bgpInstance) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Creates a Mikrotik BGP Instance.", + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + Description: "ID of this resource.", + }, + "name": schema.StringAttribute{ Required: true, Description: "The name of the BGP instance.", }, - "as": { - Type: schema.TypeInt, + "as": schema.Int64Attribute{ Required: true, Description: "The 32-bit BGP autonomous system number. Must be a value within 0 to 4294967295.", }, - "client_to_client_reflection": { - Type: schema.TypeBool, + "client_to_client_reflection": schema.BoolAttribute{ Optional: true, - Default: true, - Description: "The comment of the IP Pool to be created.", + Computed: true, + Default: booldefault.StaticBool(true), + Description: "In case this instance is a route reflector: whether to redistribute routes learned from one routing reflection client to other clients.", }, - "comment": { - Type: schema.TypeString, + "comment": schema.StringAttribute{ Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), Description: "The comment of the BGP instance to be created.", }, - "confederation_peers": { - Type: schema.TypeString, + "confederation_peers": schema.StringAttribute{ Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), Description: "List of AS numbers internal to the [local] confederation. For example: `10,20,30-50`.", }, - "disabled": { - Type: schema.TypeBool, + "disabled": schema.BoolAttribute{ Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), Description: "Whether instance is disabled.", }, - "ignore_as_path_len": { - Type: schema.TypeBool, + "ignore_as_path_len": schema.BoolAttribute{ Optional: true, - Default: false, + Computed: true, + Default: booldefault.StaticBool(false), Description: "Whether to ignore AS_PATH attribute in BGP route selection algorithm.", }, - "out_filter": { - Type: schema.TypeString, + "out_filter": schema.StringAttribute{ Optional: true, - Default: "", + Computed: true, + Default: stringdefault.StaticString(""), Description: "Output routing filter chain used by all BGP peers belonging to this instance.", }, - "redistribute_connected": { - Type: schema.TypeBool, + "redistribute_connected": schema.BoolAttribute{ Optional: true, - Default: false, + Computed: true, + Default: booldefault.StaticBool(false), Description: "If enabled, this BGP instance will redistribute the information about connected routes.", }, - "redistribute_ospf": { - Type: schema.TypeBool, + "redistribute_ospf": schema.BoolAttribute{ Optional: true, - Default: false, + Computed: true, + Default: booldefault.StaticBool(false), Description: "If enabled, this BGP instance will redistribute the information about routes learned by OSPF.", }, - "redistribute_other_bgp": { - Type: schema.TypeBool, + "redistribute_other_bgp": schema.BoolAttribute{ Optional: true, - Default: false, + Computed: true, + Default: booldefault.StaticBool(false), Description: "If enabled, this BGP instance will redistribute the information about routes learned by other BGP instances.", }, - "redistribute_rip": { - Type: schema.TypeBool, + "redistribute_rip": schema.BoolAttribute{ Optional: true, - Default: false, + Computed: true, + Default: booldefault.StaticBool(false), Description: "If enabled, this BGP instance will redistribute the information about routes learned by RIP.", }, - "redistribute_static": { - Type: schema.TypeBool, + "redistribute_static": schema.BoolAttribute{ Optional: true, - Default: false, + Computed: true, + Default: booldefault.StaticBool(false), Description: "If enabled, the router will redistribute the information about static routes added to its routing database.", }, - "router_id": { - Type: schema.TypeString, + "router_id": schema.StringAttribute{ Required: true, Description: "BGP Router ID (for this instance). If set to 0.0.0.0, BGP will use one of router's IP addresses.", }, - "routing_table": { - Type: schema.TypeString, + "routing_table": schema.StringAttribute{ Optional: true, - Default: "", + Computed: true, + Default: stringdefault.StaticString(""), Description: "Name of routing table this BGP instance operates on. ", }, - "cluster_id": { - Type: schema.TypeString, + "cluster_id": schema.StringAttribute{ Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), Description: "In case this instance is a route reflector: cluster ID of the router reflector cluster this instance belongs to.", }, - "confederation": { - Type: schema.TypeInt, + "confederation": schema.Int64Attribute{ Optional: true, + Computed: true, + Default: int64default.StaticInt64(0), Description: "In case of BGP confederations: autonomous system number that identifies the [local] confederation as a whole.", }, }, } } -func resourceBgpInstanceCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - instance := prepareBgpInstance(d) - - c := m.(*client.Mikrotik) - - bgpInstance, err := c.AddBgpInstance(instance) - if err != nil { - return diag.FromErr(err) - } - - return bgpInstanceToData(bgpInstance, d) +// Create creates the resource and sets the initial Terraform state. +func (r *bgpInstance) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var terraformModel bgpInstanceModel + var mikrotikModel client.BgpInstance + GenericCreateResource(&terraformModel, &mikrotikModel, r.client)(ctx, req, resp) } -func resourceBgpInstanceRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - c := m.(*client.Mikrotik) - - bgpInstance, err := c.FindBgpInstance(d.Id()) - - if _, ok := err.(client.LegacyBgpUnsupported); ok { - return diag.FromErr(err) - } - - if client.IsNotFoundError(err) { - d.SetId("") - return nil - } - if err != nil { - return diag.FromErr(err) - } - - return bgpInstanceToData(bgpInstance, d) +// Read refreshes the Terraform state with the latest data. +func (r *bgpInstance) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var terraformModel bgpInstanceModel + var mikrotikModel client.BgpInstance + GenericReadResource(&terraformModel, &mikrotikModel, r.client)(ctx, req, resp) } -func resourceBgpInstanceUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - c := m.(*client.Mikrotik) - - currentBgpInstance, err := c.FindBgpInstance(d.Get("name").(string)) - if _, ok := err.(client.LegacyBgpUnsupported); ok { - return diag.FromErr(err) - } - - instance := prepareBgpInstance(d) - instance.Id = currentBgpInstance.Id - - bgpInstance, err := c.UpdateBgpInstance(instance) - - if err != nil { - return diag.FromErr(err) - } - - return bgpInstanceToData(bgpInstance, d) +// Update updates the resource and sets the updated Terraform state on success. +func (r *bgpInstance) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var terraformModel bgpInstanceModel + var mikrotikModel client.BgpInstance + GenericUpdateResource(&terraformModel, &mikrotikModel, r.client)(ctx, req, resp) } -func resourceBgpInstanceDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - c := m.(*client.Mikrotik) - - err := c.DeleteBgpInstance(d.Get("name").(string)) - if _, ok := err.(client.LegacyBgpUnsupported); ok { - return diag.FromErr(err) - } - - if err != nil { - return diag.FromErr(err) - } - - d.SetId("") - return nil +// Delete deletes the resource and removes the Terraform state on success. +func (r *bgpInstance) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var terraformModel bgpInstanceModel + var mikrotikModel client.BgpInstance + GenericDeleteResource(&terraformModel, &mikrotikModel, r.client)(ctx, req, resp) } -func bgpInstanceToData(b *client.BgpInstance, d *schema.ResourceData) diag.Diagnostics { - values := map[string]interface{}{ - "name": b.Name, - "as": b.As, - "client_to_client_reflection": b.ClientToClientReflection, - "comment": b.Comment, - "confederation_peers": b.ConfederationPeers, - "disabled": b.Disabled, - "ignore_as_path_len": b.IgnoreAsPathLen, - "out_filter": b.OutFilter, - "redistribute_connected": b.RedistributeConnected, - "redistribute_ospf": b.RedistributeOspf, - "redistribute_other_bgp": b.RedistributeOtherBgp, - "redistribute_rip": b.RedistributeRip, - "redistribute_static": b.RedistributeStatic, - "router_id": b.RouterID, - "routing_table": b.RoutingTable, - "cluster_id": b.ClusterID, - "confederation": b.Confederation, - } - - d.SetId(b.Name) - - var diags diag.Diagnostics - - for key, value := range values { - if err := d.Set(key, value); err != nil { - diags = append(diags, diag.Errorf("failed to set %s: %v", key, err)...) - } - } - - return diags +func (r *bgpInstance) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + // Retrieve import ID and save to id attribute + resource.ImportStatePassthroughID(ctx, path.Root("name"), req, resp) } -func prepareBgpInstance(d *schema.ResourceData) *client.BgpInstance { - return &client.BgpInstance{ - Name: d.Get("name").(string), - As: d.Get("as").(int), - ClientToClientReflection: d.Get("client_to_client_reflection").(bool), - Comment: d.Get("comment").(string), - ConfederationPeers: d.Get("confederation_peers").(string), - Disabled: d.Get("disabled").(bool), - IgnoreAsPathLen: d.Get("ignore_as_path_len").(bool), - OutFilter: d.Get("out_filter").(string), - RedistributeConnected: d.Get("redistribute_connected").(bool), - RedistributeOspf: d.Get("redistribute_ospf").(bool), - RedistributeOtherBgp: d.Get("redistribute_other_bgp").(bool), - RedistributeRip: d.Get("redistribute_rip").(bool), - RedistributeStatic: d.Get("redistribute_static").(bool), - RouterID: d.Get("router_id").(string), - RoutingTable: d.Get("routing_table").(string), - ClusterID: d.Get("cluster_id").(string), - Confederation: d.Get("confederation").(int), - } +type bgpInstanceModel struct { + Id tftypes.String `tfsdk:"id"` + Name tftypes.String `tfsdk:"name"` + As tftypes.Int64 `tfsdk:"as"` + ClientToClientReflection tftypes.Bool `tfsdk:"client_to_client_reflection"` + Comment tftypes.String `tfsdk:"comment"` + ConfederationPeers tftypes.String `tfsdk:"confederation_peers"` + Disabled tftypes.Bool `tfsdk:"disabled"` + IgnoreAsPathLen tftypes.Bool `tfsdk:"ignore_as_path_len"` + OutFilter tftypes.String `tfsdk:"out_filter"` + RedistributeConnected tftypes.Bool `tfsdk:"redistribute_connected"` + RedistributeOspf tftypes.Bool `tfsdk:"redistribute_ospf"` + RedistributeOtherBgp tftypes.Bool `tfsdk:"redistribute_other_bgp"` + RedistributeRip tftypes.Bool `tfsdk:"redistribute_rip"` + RedistributeStatic tftypes.Bool `tfsdk:"redistribute_static"` + RouterID tftypes.String `tfsdk:"router_id"` + RoutingTable tftypes.String `tfsdk:"routing_table"` + ClusterID tftypes.String `tfsdk:"cluster_id"` + Confederation tftypes.Int64 `tfsdk:"confederation"` } diff --git a/mikrotik/resource_bgp_instance_test.go b/mikrotik/resource_bgp_instance_test.go index 019724c..5a087c4 100644 --- a/mikrotik/resource_bgp_instance_test.go +++ b/mikrotik/resource_bgp_instance_test.go @@ -72,14 +72,22 @@ func TestAccMikrotikBgpInstance_createAndPlanWithNonExistantBgpInstance(t *testi PreCheck: func() { testAccPreCheck(t) }, ProtoV5ProviderFactories: testAccProtoV5ProviderFactories, CheckDestroy: testAccCheckMikrotikBgpInstanceDestroy, + Steps: []resource.TestStep{ { Config: testAccBgpInstance(name, as, routerId), Check: resource.ComposeTestCheckFunc( testAccBgpInstanceExists(resourceName), resource.TestCheckResourceAttrSet(resourceName, "id"), - testAccCheckResourceDisappears(testAccProvider, resourceBgpInstance(), resourceName), ), + }, + { + Config: testAccBgpInstance(name, as, routerId), + Destroy: true, + }, + { + Config: testAccBgpInstance(name, as, routerId), + PlanOnly: true, ExpectNonEmptyPlan: true, }, }, @@ -160,6 +168,7 @@ func TestAccMikrotikBgpInstance_import(t *testing.T) { }, { ResourceName: resourceName, + ImportStateId: name, ImportState: true, ImportStateVerify: true, }, @@ -212,7 +221,7 @@ func testAccBgpInstanceExists(resourceName string) resource.TestCheckFunc { return fmt.Errorf("mikrotik_bgp_instance does not exist in the statefile") } - _, err := apiClient.FindBgpInstance(rs.Primary.ID) + _, err := apiClient.FindBgpInstance(rs.Primary.Attributes["name"]) if err != nil { return fmt.Errorf("Unable to get the bgp instance with error: %v", err) @@ -228,7 +237,7 @@ func testAccCheckMikrotikBgpInstanceDestroy(s *terraform.State) error { continue } - bgpInstance, err := apiClient.FindBgpInstance(rs.Primary.ID) + bgpInstance, err := apiClient.FindBgpInstance(rs.Primary.Attributes["name"]) if !client.IsNotFoundError(err) && err != nil { return err