From 3cceb7fc3d997eabfad3b8da119e0932c800e6f9 Mon Sep 17 00:00:00 2001 From: Jose Luis Pedrosa Date: Tue, 26 Sep 2023 22:05:52 +0100 Subject: [PATCH 01/10] Improvements on the resource routeros_interface_ethernet Add examples for routeros_interface_ethernet Add additional computed properties of the routeros_interface_ethernet resource --- .../routeros_interface_ethernet/import.sh | 3 + .../routeros_interface_ethernet/resource.tf | 5 ++ routeros/resource_default_actions.go | 4 +- routeros/resource_interface_ethernet.go | 84 +++++++++++++++++++ 4 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 examples/resources/routeros_interface_ethernet/import.sh create mode 100644 examples/resources/routeros_interface_ethernet/resource.tf diff --git a/examples/resources/routeros_interface_ethernet/import.sh b/examples/resources/routeros_interface_ethernet/import.sh new file mode 100644 index 00000000..cb69de42 --- /dev/null +++ b/examples/resources/routeros_interface_ethernet/import.sh @@ -0,0 +1,3 @@ +#The ID can be found via API or the terminal +#The command for the terminal is -> :put [/interface/ethernet get [print show-ids]] +terraform import routeros_interface_ethernet.test "*0" diff --git a/examples/resources/routeros_interface_ethernet/resource.tf b/examples/resources/routeros_interface_ethernet/resource.tf new file mode 100644 index 00000000..8027cf3e --- /dev/null +++ b/examples/resources/routeros_interface_ethernet/resource.tf @@ -0,0 +1,5 @@ +resource "routeros_interface_ethernet" "test" { + factory_name = "sfp-sfpplus8" + name = "swtich-eth0" + mtu = 9000 +} diff --git a/routeros/resource_default_actions.go b/routeros/resource_default_actions.go index 7e8c9f9a..89203fb0 100644 --- a/routeros/resource_default_actions.go +++ b/routeros/resource_default_actions.go @@ -44,7 +44,7 @@ func ResourceCreate(ctx context.Context, s map[string]*schema.Schema, d *schema. } // ... If no ID is set, Terraform assumes the resource was not created successfully; - // as a result, no state will be saved for that resource. + // as a result, no state will be saved for that resource.go if res.GetID(Id) == "" { return diag.Diagnostics{ diag.Diagnostic{ @@ -174,7 +174,7 @@ func SystemResourceRead(ctx context.Context, s map[string]*schema.Schema, d *sch return diag.FromErr(err) } - // We make a unique Id, it does not affect the work with the Mikrotik. + // We make a unique Id, it does not affect the work with the Mikrotik. // Id: /caps-man/manager -> caps-man.manager d.SetId(strings.ReplaceAll(strings.TrimLeft(metadata.Path, "/"), "/", ".")) diff --git a/routeros/resource_interface_ethernet.go b/routeros/resource_interface_ethernet.go index 3e726e9d..4da3dad0 100644 --- a/routeros/resource_interface_ethernet.go +++ b/routeros/resource_interface_ethernet.go @@ -44,6 +44,8 @@ import ( */ const poeOutField = "poe_out" +const cableSettingsField = "cable_settings" +const switchField = "switch" // https://help.mikrotik.com/docs/display/ROS/Ethernet#Ethernet-Properties func ResourceInterfaceEthernet() *schema.Resource { @@ -109,6 +111,26 @@ func ResourceInterfaceEthernet() *schema.Resource { Default: true, Optional: true, }, + "driver_rx_byte": { + Type: schema.TypeInt, + Computed: true, + Description: `Total count of received bytes on device CPU`, + }, + "driver_rx_packet": { + Type: schema.TypeInt, + Computed: true, + Description: `Total count of received packets on device CPU`, + }, + "driver_tx_byte": { + Type: schema.TypeInt, + Computed: true, + Description: `Total count of transmitted packets by device CPU`, + }, + "driver_tx_packet": { + Type: schema.TypeInt, + Computed: true, + Description: `Total count of transmitted packets by device CPU`, + }, "factory_name": { Type: schema.TypeString, Optional: false, @@ -199,6 +221,16 @@ func ResourceInterfaceEthernet() *schema.Resource { Computed: true, Description: "Total count of received bytes.", }, + "rx_error_events": { + Type: schema.TypeInt, + Computed: true, + Description: `Total count of received frames with active error event`, + }, + "rx_jabber": { + Type: schema.TypeInt, + Computed: true, + Description: "Total count of received jabbed packets - a packet that is transmitted longer than the maximum packet length", + }, "rx_multicast": { Type: schema.TypeInt, Computed: true, @@ -217,6 +249,28 @@ func ResourceInterfaceEthernet() *schema.Resource { Optional: true, ValidateFunc: validation.StringInSlice([]string{"on", "off", "auto"}, false), }, + "rx_too_long": { + Type: schema.TypeInt, + Description: `Total count of received frames that were larger than the maximum supported frame size by the network device, see the max-l2mtu property`, + Computed: true, + }, + "rx_too_short": { + Type: schema.TypeInt, + Computed: true, + Description: `Total count of received frame shorter than the minimum 64 bytes`, + }, + "rx_unicast": { + Type: schema.TypeInt, + Computed: true, + Description: `Total count of received unicast frames`, + }, + "sfp_rate_select": { + Type: schema.TypeString, + Optional: true, + Description: `Allows to control rate select pin for SFP ports. Values: high | low`, + Default: "high", + ValidateFunc: validation.StringInSlice([]string{"high", "low"}, false), + }, "sfp_shutdown_temperature": { Type: schema.TypeInt, Description: "The temperature in Celsius at which the interface will be temporarily turned off due to too high detected SFP module temperature (introduced v6.48)." + @@ -263,11 +317,31 @@ func ResourceInterfaceEthernet() *schema.Resource { Computed: true, Description: "Total count of transmitted multicast frames.", }, + "tx_collision": { + Type: schema.TypeInt, + Computed: true, + Description: "Total count of transmitted frames that made collisions", + }, + "tx_drop": { + Type: schema.TypeInt, + Computed: true, + Description: "Total count of transmitted frames that were dropped due to already full output queue", + }, + "tx_late_collision": { + Type: schema.TypeInt, + Computed: true, + Description: "Total count of transmitted frames that made collision after being already halfway transmitted", + }, "tx_packet": { Type: schema.TypeInt, Computed: true, Description: "Total count of transmitted packets.", }, + "tx_pause": { + Type: schema.TypeInt, + Computed: true, + Description: "Total count of transmitted pause frames.", + }, } return &schema.Resource{ @@ -303,6 +377,16 @@ func UpdateOnlyDeviceCreate(s map[string]*schema.Schema) schema.CreateContextFun s[MetaSkipFields].Default = fmt.Sprintf("%s,\"%s\"", s[MetaSkipFields].Default, poeOutField) } + _, supportsCableSettings := ethernetInterface["cable-settings"] + if !supportsCableSettings { + s[MetaSkipFields].Default = fmt.Sprintf("%s,\"%s\"", s[MetaSkipFields].Default, cableSettingsField) + } + + _, supportsSwitch := ethernetInterface["switch"] + if !supportsSwitch { + s[MetaSkipFields].Default = fmt.Sprintf("%s,\"%s\"", s[MetaSkipFields].Default, switchField) + } + d.SetId(ethernetInterface.GetID(Id)) if updateDiag := ResourceUpdate(ctx, s, d, m); updateDiag.HasError() { return updateDiag From f4b8f196d12019ff1d6221e2b41325f442dc7c4d Mon Sep 17 00:00:00 2001 From: Jose Luis Pedrosa Date: Tue, 26 Sep 2023 22:12:56 +0100 Subject: [PATCH 02/10] switch is read only --- routeros/resource_interface_ethernet.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/routeros/resource_interface_ethernet.go b/routeros/resource_interface_ethernet.go index 4da3dad0..b55f2fc7 100644 --- a/routeros/resource_interface_ethernet.go +++ b/routeros/resource_interface_ethernet.go @@ -382,11 +382,6 @@ func UpdateOnlyDeviceCreate(s map[string]*schema.Schema) schema.CreateContextFun s[MetaSkipFields].Default = fmt.Sprintf("%s,\"%s\"", s[MetaSkipFields].Default, cableSettingsField) } - _, supportsSwitch := ethernetInterface["switch"] - if !supportsSwitch { - s[MetaSkipFields].Default = fmt.Sprintf("%s,\"%s\"", s[MetaSkipFields].Default, switchField) - } - d.SetId(ethernetInterface.GetID(Id)) if updateDiag := ResourceUpdate(ctx, s, d, m); updateDiag.HasError() { return updateDiag From 59dcde3468ef3dcf093affb4431590a33beb1a9c Mon Sep 17 00:00:00 2001 From: Jose Luis Pedrosa Date: Thu, 28 Sep 2023 09:05:21 +0100 Subject: [PATCH 03/10] PR comments --- routeros/resource_default_actions.go | 2 +- routeros/resource_interface_ethernet.go | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/routeros/resource_default_actions.go b/routeros/resource_default_actions.go index 89203fb0..b66a8c87 100644 --- a/routeros/resource_default_actions.go +++ b/routeros/resource_default_actions.go @@ -44,7 +44,7 @@ func ResourceCreate(ctx context.Context, s map[string]*schema.Schema, d *schema. } // ... If no ID is set, Terraform assumes the resource was not created successfully; - // as a result, no state will be saved for that resource.go + // as a result, no state will be saved for that resource if res.GetID(Id) == "" { return diag.Diagnostics{ diag.Diagnostic{ diff --git a/routeros/resource_interface_ethernet.go b/routeros/resource_interface_ethernet.go index b55f2fc7..bfc1c71c 100644 --- a/routeros/resource_interface_ethernet.go +++ b/routeros/resource_interface_ethernet.go @@ -45,7 +45,6 @@ import ( const poeOutField = "poe_out" const cableSettingsField = "cable_settings" -const switchField = "switch" // https://help.mikrotik.com/docs/display/ROS/Ethernet#Ethernet-Properties func ResourceInterfaceEthernet() *schema.Resource { @@ -377,8 +376,7 @@ func UpdateOnlyDeviceCreate(s map[string]*schema.Schema) schema.CreateContextFun s[MetaSkipFields].Default = fmt.Sprintf("%s,\"%s\"", s[MetaSkipFields].Default, poeOutField) } - _, supportsCableSettings := ethernetInterface["cable-settings"] - if !supportsCableSettings { + if _, supportsCableSettings := ethernetInterface["cable-settings"]; supportsCableSettings { s[MetaSkipFields].Default = fmt.Sprintf("%s,\"%s\"", s[MetaSkipFields].Default, cableSettingsField) } From ca94aff7648dfe5c700f446ac49fcba47f5e8a5a Mon Sep 17 00:00:00 2001 From: Jose Luis Pedrosa Date: Thu, 28 Sep 2023 10:31:46 +0100 Subject: [PATCH 04/10] Temp, issues on updates --- routeros/resource_default_actions.go | 2 +- routeros/resource_interface_ethernet.go | 153 ++++++++++++++++++++---- 2 files changed, 128 insertions(+), 27 deletions(-) diff --git a/routeros/resource_default_actions.go b/routeros/resource_default_actions.go index b66a8c87..1a2368fd 100644 --- a/routeros/resource_default_actions.go +++ b/routeros/resource_default_actions.go @@ -44,7 +44,7 @@ func ResourceCreate(ctx context.Context, s map[string]*schema.Schema, d *schema. } // ... If no ID is set, Terraform assumes the resource was not created successfully; - // as a result, no state will be saved for that resource + // as a result, no state will be saved for that resource. if res.GetID(Id) == "" { return diag.Diagnostics{ diag.Diagnostic{ diff --git a/routeros/resource_interface_ethernet.go b/routeros/resource_interface_ethernet.go index bfc1c71c..9b780139 100644 --- a/routeros/resource_interface_ethernet.go +++ b/routeros/resource_interface_ethernet.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "strings" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -45,6 +46,7 @@ import ( const poeOutField = "poe_out" const cableSettingsField = "cable_settings" +const runningCheckField = "disable_running_check" // https://help.mikrotik.com/docs/display/ROS/Ethernet#Ethernet-Properties func ResourceInterfaceEthernet() *schema.Resource { @@ -75,10 +77,11 @@ func ResourceInterfaceEthernet() *schema.Resource { Note2: Gigabit Ethernet and NBASE-T Ethernet links cannot work with auto-negotiation disabled.`, }, "bandwidth": { - Type: schema.TypeInt, + Type: schema.TypeString, Optional: true, Description: `Sets max rx/tx bandwidth in kbps that will be handled by an interface. TX limit is supported on all Atheros switch-chip ports. RX limit is supported only on Atheros8327/QCA8337 switch-chip ports.`, + DiffSuppressFunc: AlwaysPresentNotUserProvided, }, "cable_settings": { Type: schema.TypeString, @@ -225,6 +228,16 @@ func ResourceInterfaceEthernet() *schema.Resource { Computed: true, Description: `Total count of received frames with active error event`, }, + "rx_fcs_error": { + Type: schema.TypeInt, + Computed: true, + Description: "Total count of received frames with incorrect checksum", + }, + "rx_fragment": { + Type: schema.TypeInt, + Computed: true, + Description: `Total count of received fragmented frames (not related to IP fragmentation)`, + }, "rx_jabber": { Type: schema.TypeInt, Computed: true, @@ -240,6 +253,11 @@ func ResourceInterfaceEthernet() *schema.Resource { Computed: true, Description: "Total count of received packets.", }, + "rx_pause": { + Type: schema.TypeInt, + Computed: true, + Description: "Total count of received pause frames", + }, "rx_flow_control": { Type: schema.TypeString, Description: `When set to on, the port will process received pause frames and suspend transmission if required. @@ -248,6 +266,11 @@ func ResourceInterfaceEthernet() *schema.Resource { Optional: true, ValidateFunc: validation.StringInSlice([]string{"on", "off", "auto"}, false), }, + "rx_overflow": { + Type: schema.TypeInt, + Description: `Total count of received overflowed frames, can be caused when device resources are insufficient to receive a certain frame`, + Computed: true, + }, "rx_too_long": { Type: schema.TypeInt, Description: `Total count of received frames that were larger than the maximum supported frame size by the network device, see the max-l2mtu property`, @@ -288,7 +311,7 @@ func ResourceInterfaceEthernet() *schema.Resource { ValidateFunc: validation.StringInSlice([]string{"10Mbps", "10Gbps", "100Mbps", "1Gbps"}, false), }, "switch": { - Type: schema.TypeInt, + Type: schema.TypeString, Description: "ID to which switch chip interface belongs to.", Computed: true, }, @@ -326,6 +349,7 @@ func ResourceInterfaceEthernet() *schema.Resource { Computed: true, Description: "Total count of transmitted frames that were dropped due to already full output queue", }, + "tx_late_collision": { Type: schema.TypeInt, Computed: true, @@ -341,12 +365,52 @@ func ResourceInterfaceEthernet() *schema.Resource { Computed: true, Description: "Total count of transmitted pause frames.", }, + "tx_rx_64": { + Type: schema.TypeInt, + Computed: true, + Description: "Total count of transmitted and received 64 byte frames", + }, + "tx_rx_65_127": { + Type: schema.TypeInt, + Computed: true, + Description: "Total count of transmitted and received 64 to 127 byte frames", + }, + "tx_rx_128_255": { + Type: schema.TypeInt, + Computed: true, + Description: "Total count of transmitted and received 128 to 255 byte frames", + }, + "tx_rx_256_511": { + Type: schema.TypeInt, + Computed: true, + Description: "Total count of transmitted and received 256 to 511 byte frames", + }, + "tx_rx_512_1023": { + Type: schema.TypeInt, + Computed: true, + Description: "Total count of transmitted and received 512 to 1024 byte frames", + }, + "tx_rx_1024_max": { + Type: schema.TypeInt, + Computed: true, + Description: "Total count of transmitted and received 1024 or above byte frames", + }, + "tx_underrun": { + Type: schema.TypeInt, + Computed: true, + Description: "Total count of transmitted underrun packets", + }, + "tx_unicast": { + Type: schema.TypeInt, + Computed: true, + Description: "Total count of transmitted unicast frames.", + }, } return &schema.Resource{ CreateContext: UpdateOnlyDeviceCreate(resSchema), - ReadContext: DefaultRead(resSchema), - UpdateContext: DefaultUpdate(resSchema), + ReadContext: UpdateOnlyDeviceRead(resSchema), + UpdateContext: UpdateOnlyDeviceUpdate(resSchema), DeleteContext: NoOpDelete, Importer: &schema.ResourceImporter{ @@ -359,34 +423,67 @@ func ResourceInterfaceEthernet() *schema.Resource { func UpdateOnlyDeviceCreate(s map[string]*schema.Schema) schema.CreateContextFunc { return func(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - ethernetInterface, err := findInterfaceByDefaultName(s, d, m.(Client)) - if err != nil { - return diag.FromErr(err) - } + return UpdateEthernetInterface(ctx, s, d, m) + } +} - // Router won't accept poe-out parameter if the interface does not support it. - poeDesiredState := d.Get(poeOutField) - _, supportsPoE := ethernetInterface[SnakeToKebab(poeOutField)] - switch { - // if the user has specified it, but it's not supported, let's error out - case poeDesiredState != "off" && !supportsPoE: - return diag.FromErr(errors.New("can't configure PoE, router does not supports it")) - // if the router does not support PoE, avoid sending the parameter as it returns an error. - case !supportsPoE: - s[MetaSkipFields].Default = fmt.Sprintf("%s,\"%s\"", s[MetaSkipFields].Default, poeOutField) - } +func UpdateOnlyDeviceUpdate(s map[string]*schema.Schema) schema.UpdateContextFunc { + return func(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + return UpdateEthernetInterface(ctx, s, d, m) + } +} - if _, supportsCableSettings := ethernetInterface["cable-settings"]; supportsCableSettings { - s[MetaSkipFields].Default = fmt.Sprintf("%s,\"%s\"", s[MetaSkipFields].Default, cableSettingsField) - } +func UpdateOnlyDeviceRead(s map[string]*schema.Schema) schema.ReadContextFunc { + return func(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + return DefaultRead(s)(ctx, d, m) + } +} + +func UpdateEthernetInterface(ctx context.Context, s map[string]*schema.Schema, d *schema.ResourceData, m interface{}) diag.Diagnostics { + ethernetInterface, err := findInterfaceByDefaultName(s, d, m.(Client)) + if err != nil { + return diag.FromErr(err) + } + + // Router won't accept poe-out parameter if the interface does not support it. + poeDesiredState := d.Get(poeOutField) + _, supportsPoE := ethernetInterface[SnakeToKebab(poeOutField)] + switch { + // if the user has specified it, but it's not supported, let's error out + case poeDesiredState != "off" && !supportsPoE: + return diag.FromErr(errors.New("can't configure PoE, router does not supports it")) + // if the router does not support PoE, avoid sending the parameter as it returns an error. + case !supportsPoE: + skipFieldInSchema(s, poeOutField) + } + + if _, supportsCableSettings := ethernetInterface["cable-settings"]; supportsCableSettings { + skipFieldInSchema(s, cableSettingsField) + } + + if _, supportsRunningCheck := ethernetInterface["disable-running-check"]; supportsRunningCheck { + skipFieldInSchema(s, runningCheckField) + } - d.SetId(ethernetInterface.GetID(Id)) - if updateDiag := ResourceUpdate(ctx, s, d, m); updateDiag.HasError() { - return updateDiag + // Dynamic schema, counters for tx_queue packets, changes from router to router, read only countes. + for key, _ := range ethernetInterface { + if strings.HasPrefix(key, "tx_queue") { + fmt.Printf("adding dynamic schema: %s", key) + s[key] = &schema.Schema{ + Type: schema.TypeInt, + Computed: true, + Description: "Total count of interface for numbered queue", + } } + } - return ResourceRead(ctx, s, d, m) + d.SetId(ethernetInterface.GetID(Id)) + if updateDiag := ResourceUpdate(ctx, s, d, m); updateDiag.HasError() { + return updateDiag } + + return ResourceRead(ctx, s, d, m) + } func NoOpDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { @@ -408,3 +505,7 @@ func findInterfaceByDefaultName(s map[string]*schema.Schema, d *schema.ResourceD ethernetInterface := (*items)[0] return ethernetInterface, nil } + +func skipFieldInSchema(s map[string]*schema.Schema, field string) { + s[MetaSkipFields].Default = fmt.Sprintf("%s,\"%s\"", s[MetaSkipFields].Default, field) +} From e583a5586d39edc0da90a76a8fd4b4e9cd48dbe5 Mon Sep 17 00:00:00 2001 From: Jose Luis Pedrosa Date: Thu, 28 Sep 2023 20:17:51 +0100 Subject: [PATCH 05/10] Avoid hardcoded defaults --- routeros/resource_interface_ethernet.go | 73 +++++++++++++------------ 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/routeros/resource_interface_ethernet.go b/routeros/resource_interface_ethernet.go index 9b780139..d2d02fc8 100644 --- a/routeros/resource_interface_ethernet.go +++ b/routeros/resource_interface_ethernet.go @@ -48,6 +48,7 @@ const poeOutField = "poe_out" const cableSettingsField = "cable_settings" const runningCheckField = "disable_running_check" +// ResourceInterfaceEthernet is the schema for ethernet interfaces // https://help.mikrotik.com/docs/display/ROS/Ethernet#Ethernet-Properties func ResourceInterfaceEthernet() *schema.Resource { resSchema := map[string]*schema.Schema{ @@ -65,6 +66,7 @@ func ResourceInterfaceEthernet() *schema.Resource { ValidateFunc: validation.StringInSlice([]string{ "10M-full", "10M-half", "100M-full", "100M-half", "1000M-full", "1000M-half", "2500M-full", "5000M-full", "10000M-full"}, false), + DiffSuppressFunc: AlwaysPresentNotUserProvided, }, KeyArp: PropArpRw, KeyArpTimeout: PropArpTimeoutRw, @@ -84,20 +86,20 @@ func ResourceInterfaceEthernet() *schema.Resource { DiffSuppressFunc: AlwaysPresentNotUserProvided, }, "cable_settings": { - Type: schema.TypeString, - Optional: true, - Default: "default", - Description: `Changes the cable length setting (only applicable to NS DP83815/6 cards)`, - ValidateFunc: validation.StringInSlice([]string{"default", "short", "standard"}, false), + Type: schema.TypeString, + Optional: true, + Description: `Changes the cable length setting (only applicable to NS DP83815/6 cards)`, + ValidateFunc: validation.StringInSlice([]string{"default", "short", "standard"}, false), + DiffSuppressFunc: AlwaysPresentNotUserProvided, }, "combo_mode": { Type: schema.TypeString, Optional: true, - Default: "auto", Description: `When auto mode is selected, the port that was first connected will establish the link. In case this link fails, the other port will try to establish a new link. If both ports are connected at the same time (e.g. after reboot), the priority will be the SFP/SFP+ port. When sfp mode is selected, the interface will only work through SFP/SFP+ cage. When copper mode is selected, the interface will only work through RJ45 Ethernet port.`, - ValidateFunc: validation.StringInSlice([]string{"auto", "copper", "sfp"}, false), + ValidateFunc: validation.StringInSlice([]string{"auto", "copper", "sfp"}, false), + DiffSuppressFunc: AlwaysPresentNotUserProvided, }, KeyComment: PropCommentRw, "default_name": { @@ -171,10 +173,10 @@ func ResourceInterfaceEthernet() *schema.Resource { Computed: true, }, "mac_address": { - Type: schema.TypeString, - Description: `Media Access Control number of an interface.`, - Optional: true, - Computed: true, + Type: schema.TypeString, + Description: `Media Access Control number of an interface.`, + Optional: true, + DiffSuppressFunc: AlwaysPresentNotUserProvided, }, "mdix_enable": { Type: schema.TypeBool, @@ -196,11 +198,12 @@ func ResourceInterfaceEthernet() *schema.Resource { Computed: true, }, poeOutField: { - Type: schema.TypeString, - Description: "PoE settings: (https://wiki.mikrotik.com/wiki/Manual:PoE-Out)", - Default: "off", - Optional: true, - ValidateFunc: validation.StringInSlice([]string{"auto-on", "forced-on", "off"}, false), + Type: schema.TypeString, + Description: "PoE settings: (https://wiki.mikrotik.com/wiki/Manual:PoE-Out)", + Default: "off", + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"auto-on", "forced-on", "off"}, false), + DiffSuppressFunc: AlwaysPresentNotUserProvided, }, "poe_priority": { Type: schema.TypeInt, @@ -262,9 +265,10 @@ func ResourceInterfaceEthernet() *schema.Resource { Type: schema.TypeString, Description: `When set to on, the port will process received pause frames and suspend transmission if required. auto is the same as on except when auto-negotiation=yes flow control status is resolved by taking into account what other end advertises.`, - Default: "off", - Optional: true, - ValidateFunc: validation.StringInSlice([]string{"on", "off", "auto"}, false), + Default: "off", + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"on", "off", "auto"}, false), + DiffSuppressFunc: AlwaysPresentNotUserProvided, }, "rx_overflow": { Type: schema.TypeInt, @@ -320,9 +324,10 @@ func ResourceInterfaceEthernet() *schema.Resource { Description: `When set to on, the port will generate pause frames to the upstream device to temporarily stop the packet transmission. Pause frames are only generated when some routers output interface is congested and packets cannot be transmitted anymore. Auto is the same as on except when auto-negotiation=yes flow control status is resolved by taking into account what other end advertises.`, - Default: "off", - Optional: true, - ValidateFunc: validation.StringInSlice([]string{"on", "off", "auto"}, false), + Default: "off", + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"on", "off", "auto"}, false), + DiffSuppressFunc: AlwaysPresentNotUserProvided, }, "tx_broadcast": { Type: schema.TypeInt, @@ -411,7 +416,7 @@ func ResourceInterfaceEthernet() *schema.Resource { CreateContext: UpdateOnlyDeviceCreate(resSchema), ReadContext: UpdateOnlyDeviceRead(resSchema), UpdateContext: UpdateOnlyDeviceUpdate(resSchema), - DeleteContext: NoOpDelete, + DeleteContext: DefaultSystemDelete(resSchema), Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, @@ -454,19 +459,19 @@ func UpdateEthernetInterface(ctx context.Context, s map[string]*schema.Schema, d return diag.FromErr(errors.New("can't configure PoE, router does not supports it")) // if the router does not support PoE, avoid sending the parameter as it returns an error. case !supportsPoE: - skipFieldInSchema(s, poeOutField) + s[MetaSkipFields].Default = skipFieldInSchema(s[MetaSkipFields].Default, poeOutField) } - if _, supportsCableSettings := ethernetInterface["cable-settings"]; supportsCableSettings { - skipFieldInSchema(s, cableSettingsField) + if _, supportsCableSettings := ethernetInterface["cable-settings"]; !supportsCableSettings { + s[MetaSkipFields].Default = skipFieldInSchema(s[MetaSkipFields].Default, cableSettingsField) } - if _, supportsRunningCheck := ethernetInterface["disable-running-check"]; supportsRunningCheck { - skipFieldInSchema(s, runningCheckField) + if _, supportsRunningCheck := ethernetInterface["disable-running-check"]; !supportsRunningCheck { + s[MetaSkipFields].Default = skipFieldInSchema(s[MetaSkipFields].Default, runningCheckField) } - // Dynamic schema, counters for tx_queue packets, changes from router to router, read only countes. - for key, _ := range ethernetInterface { + // Dynamic schema, counters for tx_queue packets, changes from router to router, read only counters. + for key := range ethernetInterface { if strings.HasPrefix(key, "tx_queue") { fmt.Printf("adding dynamic schema: %s", key) s[key] = &schema.Schema{ @@ -486,10 +491,6 @@ func UpdateEthernetInterface(ctx context.Context, s map[string]*schema.Schema, d } -func NoOpDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - return nil -} - func findInterfaceByDefaultName(s map[string]*schema.Schema, d *schema.ResourceData, c Client) (MikrotikItem, error) { metadata := GetMetadata(s) filter := buildReadFilter(map[string]interface{}{"default-name": d.Get("factory_name")}) @@ -506,6 +507,6 @@ func findInterfaceByDefaultName(s map[string]*schema.Schema, d *schema.ResourceD return ethernetInterface, nil } -func skipFieldInSchema(s map[string]*schema.Schema, field string) { - s[MetaSkipFields].Default = fmt.Sprintf("%s,\"%s\"", s[MetaSkipFields].Default, field) +func skipFieldInSchema(defaults interface{}, field string) string { + return fmt.Sprintf("%s,\"%s\"", defaults, field) } From 3774ea25602e3198ea8ee88813fcaeb722a7704f Mon Sep 17 00:00:00 2001 From: Jose Luis Pedrosa Date: Thu, 28 Sep 2023 20:39:56 +0100 Subject: [PATCH 06/10] Enable ignoring read fields that are meant to be skipped --- routeros/mikrotik_serialize.go | 12 ++++++++++++ routeros/resource_interface_ethernet.go | 25 +++++++++++++------------ 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/routeros/mikrotik_serialize.go b/routeros/mikrotik_serialize.go index a939c8f1..8bbb3086 100644 --- a/routeros/mikrotik_serialize.go +++ b/routeros/mikrotik_serialize.go @@ -269,12 +269,18 @@ func MikrotikResourceDataToTerraform(item MikrotikItem, s map[string]*schema.Sch var diags diag.Diagnostics var err error var transformSet map[string]string + var skipFields map[string]struct{} // {"channel": "channel.config", "mikrotik-field-name": "schema-field-name"} if ts, ok := s[MetaTransformSet]; ok { transformSet = loadTransformSet(ts.Default.(string), false) } + // "field_first", "field_second", "field_third" + if sf, ok := s[MetaSkipFields]; ok { + skipFields = loadSkipFields(sf.Default.(string)) + } + // TypeMap,TypeSet initialization information storage. var maps = make(map[string]map[string]interface{}) var nestedLists = make(map[string]map[string]interface{}) @@ -305,6 +311,12 @@ func MikrotikResourceDataToTerraform(item MikrotikItem, s map[string]*schema.Sch // field-name => field_name terraformSnakeName := KebabToSnake(mikrotikKebabName) + if skipFields != nil { + if _, ok := skipFields[terraformSnakeName]; ok { + continue + } + } + // Composite fields. var subFieldSnakeName string if strings.Contains(terraformSnakeName, ".") { diff --git a/routeros/resource_interface_ethernet.go b/routeros/resource_interface_ethernet.go index d2d02fc8..a5cd11ef 100644 --- a/routeros/resource_interface_ethernet.go +++ b/routeros/resource_interface_ethernet.go @@ -440,6 +440,19 @@ func UpdateOnlyDeviceUpdate(s map[string]*schema.Schema) schema.UpdateContextFun func UpdateOnlyDeviceRead(s map[string]*schema.Schema) schema.ReadContextFunc { return func(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + ethernetInterface, err := findInterfaceByDefaultName(s, d, m.(Client)) + if err != nil { + return diag.FromErr(err) + } + + // Dynamic schema, counters for tx_queue${number}_packets, changes from router to router, read only counters. + // Just drop them as they don't have much sense in the context of a terraform provider + for key := range ethernetInterface { + if strings.HasPrefix(key, "tx-queue") { + s[MetaSkipFields].Default = skipFieldInSchema(s[MetaSkipFields].Default, KebabToSnake(key)) + } + } + return DefaultRead(s)(ctx, d, m) } } @@ -470,18 +483,6 @@ func UpdateEthernetInterface(ctx context.Context, s map[string]*schema.Schema, d s[MetaSkipFields].Default = skipFieldInSchema(s[MetaSkipFields].Default, runningCheckField) } - // Dynamic schema, counters for tx_queue packets, changes from router to router, read only counters. - for key := range ethernetInterface { - if strings.HasPrefix(key, "tx_queue") { - fmt.Printf("adding dynamic schema: %s", key) - s[key] = &schema.Schema{ - Type: schema.TypeInt, - Computed: true, - Description: "Total count of interface for numbered queue", - } - } - } - d.SetId(ethernetInterface.GetID(Id)) if updateDiag := ResourceUpdate(ctx, s, d, m); updateDiag.HasError() { return updateDiag From cd1bb37e9597ce18e7bad52e275054e312624b05 Mon Sep 17 00:00:00 2001 From: Jose Luis Pedrosa Date: Thu, 28 Sep 2023 20:44:03 +0100 Subject: [PATCH 07/10] Unexport funtions --- routeros/resource_interface_ethernet.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/routeros/resource_interface_ethernet.go b/routeros/resource_interface_ethernet.go index a5cd11ef..e50eceda 100644 --- a/routeros/resource_interface_ethernet.go +++ b/routeros/resource_interface_ethernet.go @@ -413,9 +413,9 @@ func ResourceInterfaceEthernet() *schema.Resource { } return &schema.Resource{ - CreateContext: UpdateOnlyDeviceCreate(resSchema), - ReadContext: UpdateOnlyDeviceRead(resSchema), - UpdateContext: UpdateOnlyDeviceUpdate(resSchema), + CreateContext: updateOnlyDeviceCreate(resSchema), + ReadContext: updateOnlyDeviceRead(resSchema), + UpdateContext: updateOnlyDeviceUpdate(resSchema), DeleteContext: DefaultSystemDelete(resSchema), Importer: &schema.ResourceImporter{ @@ -426,19 +426,19 @@ func ResourceInterfaceEthernet() *schema.Resource { } } -func UpdateOnlyDeviceCreate(s map[string]*schema.Schema) schema.CreateContextFunc { +func updateOnlyDeviceCreate(s map[string]*schema.Schema) schema.CreateContextFunc { return func(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { return UpdateEthernetInterface(ctx, s, d, m) } } -func UpdateOnlyDeviceUpdate(s map[string]*schema.Schema) schema.UpdateContextFunc { +func updateOnlyDeviceUpdate(s map[string]*schema.Schema) schema.UpdateContextFunc { return func(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { return UpdateEthernetInterface(ctx, s, d, m) } } -func UpdateOnlyDeviceRead(s map[string]*schema.Schema) schema.ReadContextFunc { +func updateOnlyDeviceRead(s map[string]*schema.Schema) schema.ReadContextFunc { return func(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { ethernetInterface, err := findInterfaceByDefaultName(s, d, m.(Client)) if err != nil { @@ -467,7 +467,7 @@ func UpdateEthernetInterface(ctx context.Context, s map[string]*schema.Schema, d poeDesiredState := d.Get(poeOutField) _, supportsPoE := ethernetInterface[SnakeToKebab(poeOutField)] switch { - // if the user has specified it, but it's not supported, let's error out + // if the user has specified it, but it's not supported, lets error out case poeDesiredState != "off" && !supportsPoE: return diag.FromErr(errors.New("can't configure PoE, router does not supports it")) // if the router does not support PoE, avoid sending the parameter as it returns an error. From 2c29417eed753f54631abef159d444a799e63ae8 Mon Sep 17 00:00:00 2001 From: Jose Luis Pedrosa Date: Fri, 29 Sep 2023 10:37:55 +0100 Subject: [PATCH 08/10] Simplify calls, make l2mtu writable as is the case for eth interfaces --- routeros/provider_schema_helpers.go | 6 ++++ routeros/resource_interface_ethernet.go | 46 ++++++++++++++----------- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/routeros/provider_schema_helpers.go b/routeros/provider_schema_helpers.go index 7d35064f..2bae66d9 100644 --- a/routeros/provider_schema_helpers.go +++ b/routeros/provider_schema_helpers.go @@ -170,6 +170,12 @@ var ( Description: "Layer2 Maximum transmission unit. " + "[See](https://wiki.mikrotik.com/wiki/Maximum_Transmission_Unit_on_RouterBoards).", } + PropL2MtuRw = &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Description: "Layer2 Maximum transmission unit. " + + "[See](https://wiki.mikrotik.com/wiki/Maximum_Transmission_Unit_on_RouterBoards).", + } PropMacAddressRo = &schema.Schema{ Type: schema.TypeString, Computed: true, diff --git a/routeros/resource_interface_ethernet.go b/routeros/resource_interface_ethernet.go index e50eceda..c7affd3b 100644 --- a/routeros/resource_interface_ethernet.go +++ b/routeros/resource_interface_ethernet.go @@ -147,7 +147,7 @@ func ResourceInterfaceEthernet() *schema.Resource { Default: true, Optional: true, }, - KeyL2Mtu: PropL2MtuRo, + KeyL2Mtu: PropL2MtuRw, "loop_protect": { Type: schema.TypeString, Optional: true, @@ -428,36 +428,32 @@ func ResourceInterfaceEthernet() *schema.Resource { func updateOnlyDeviceCreate(s map[string]*schema.Schema) schema.CreateContextFunc { return func(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - return UpdateEthernetInterface(ctx, s, d, m) + return updateEthernetInterface(ctx, s, d, m) } } func updateOnlyDeviceUpdate(s map[string]*schema.Schema) schema.UpdateContextFunc { return func(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - return UpdateEthernetInterface(ctx, s, d, m) + return updateEthernetInterface(ctx, s, d, m) } } func updateOnlyDeviceRead(s map[string]*schema.Schema) schema.ReadContextFunc { return func(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - ethernetInterface, err := findInterfaceByDefaultName(s, d, m.(Client)) - if err != nil { - return diag.FromErr(err) - } - - // Dynamic schema, counters for tx_queue${number}_packets, changes from router to router, read only counters. - // Just drop them as they don't have much sense in the context of a terraform provider - for key := range ethernetInterface { - if strings.HasPrefix(key, "tx-queue") { - s[MetaSkipFields].Default = skipFieldInSchema(s[MetaSkipFields].Default, KebabToSnake(key)) - } - } + return readEthernetInterface(ctx, s, d, m) + } +} - return DefaultRead(s)(ctx, d, m) +func readEthernetInterface(ctx context.Context, s map[string]*schema.Schema, d *schema.ResourceData, m interface{}) diag.Diagnostics { + ethernetInterface, err := findInterfaceByDefaultName(s, d, m.(Client)) + if err != nil { + return diag.FromErr(err) } + s = updateSchemaWithRouterCapabilities(s, ethernetInterface) + return DefaultRead(s)(ctx, d, m) } -func UpdateEthernetInterface(ctx context.Context, s map[string]*schema.Schema, d *schema.ResourceData, m interface{}) diag.Diagnostics { +func updateEthernetInterface(ctx context.Context, s map[string]*schema.Schema, d *schema.ResourceData, m interface{}) diag.Diagnostics { ethernetInterface, err := findInterfaceByDefaultName(s, d, m.(Client)) if err != nil { return diag.FromErr(err) @@ -475,11 +471,11 @@ func UpdateEthernetInterface(ctx context.Context, s map[string]*schema.Schema, d s[MetaSkipFields].Default = skipFieldInSchema(s[MetaSkipFields].Default, poeOutField) } - if _, supportsCableSettings := ethernetInterface["cable-settings"]; !supportsCableSettings { + if _, supportsCableSettings := ethernetInterface[SnakeToKebab(cableSettingsField)]; !supportsCableSettings { s[MetaSkipFields].Default = skipFieldInSchema(s[MetaSkipFields].Default, cableSettingsField) } - if _, supportsRunningCheck := ethernetInterface["disable-running-check"]; !supportsRunningCheck { + if _, supportsRunningCheck := ethernetInterface[SnakeToKebab(runningCheckField)]; !supportsRunningCheck { s[MetaSkipFields].Default = skipFieldInSchema(s[MetaSkipFields].Default, runningCheckField) } @@ -488,8 +484,18 @@ func UpdateEthernetInterface(ctx context.Context, s map[string]*schema.Schema, d return updateDiag } - return ResourceRead(ctx, s, d, m) + return readEthernetInterface(ctx, s, d, m) +} +func updateSchemaWithRouterCapabilities(s map[string]*schema.Schema, item MikrotikItem) map[string]*schema.Schema { + // Dynamic schema, counters for tx_queue${number}_packets, changes from router to router, read only counters. + // Just drop them as they don't have much sense in the context of a terraform provider + for key := range item { + if strings.HasPrefix(key, "tx-queue") { + s[MetaSkipFields].Default = skipFieldInSchema(s[MetaSkipFields].Default, KebabToSnake(key)) + } + } + return s } func findInterfaceByDefaultName(s map[string]*schema.Schema, d *schema.ResourceData, c Client) (MikrotikItem, error) { From c8545c6274504d17befd065ba25d07e5e0481118 Mon Sep 17 00:00:00 2001 From: Jose Luis Pedrosa Date: Fri, 29 Sep 2023 20:37:11 +0100 Subject: [PATCH 09/10] remove PropL2MtuRw from constants into ethernet --- routeros/provider_schema_helpers.go | 6 ------ routeros/resource_interface_ethernet.go | 7 ++++++- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/routeros/provider_schema_helpers.go b/routeros/provider_schema_helpers.go index 2bae66d9..7d35064f 100644 --- a/routeros/provider_schema_helpers.go +++ b/routeros/provider_schema_helpers.go @@ -170,12 +170,6 @@ var ( Description: "Layer2 Maximum transmission unit. " + "[See](https://wiki.mikrotik.com/wiki/Maximum_Transmission_Unit_on_RouterBoards).", } - PropL2MtuRw = &schema.Schema{ - Type: schema.TypeInt, - Optional: true, - Description: "Layer2 Maximum transmission unit. " + - "[See](https://wiki.mikrotik.com/wiki/Maximum_Transmission_Unit_on_RouterBoards).", - } PropMacAddressRo = &schema.Schema{ Type: schema.TypeString, Computed: true, diff --git a/routeros/resource_interface_ethernet.go b/routeros/resource_interface_ethernet.go index c7affd3b..347496e0 100644 --- a/routeros/resource_interface_ethernet.go +++ b/routeros/resource_interface_ethernet.go @@ -147,7 +147,12 @@ func ResourceInterfaceEthernet() *schema.Resource { Default: true, Optional: true, }, - KeyL2Mtu: PropL2MtuRw, + KeyL2Mtu: { + Type: schema.TypeInt, + Optional: true, + Description: "Layer2 Maximum transmission unit. " + + "[See](https://wiki.mikrotik.com/wiki/Maximum_Transmission_Unit_on_RouterBoards).", + }, "loop_protect": { Type: schema.TypeString, Optional: true, From 852415030350c9d60f9ca4e64531b4c033d98eb6 Mon Sep 17 00:00:00 2001 From: Jose Luis Pedrosa Date: Fri, 29 Sep 2023 20:48:55 +0100 Subject: [PATCH 10/10] Add comment to triger build --- routeros/resource_interface_ethernet.go | 1 + 1 file changed, 1 insertion(+) diff --git a/routeros/resource_interface_ethernet.go b/routeros/resource_interface_ethernet.go index 347496e0..b039e368 100644 --- a/routeros/resource_interface_ethernet.go +++ b/routeros/resource_interface_ethernet.go @@ -458,6 +458,7 @@ func readEthernetInterface(ctx context.Context, s map[string]*schema.Schema, d * return DefaultRead(s)(ctx, d, m) } +// updateEthernetInterface searches for the interface and disables fields not supported by the router instance func updateEthernetInterface(ctx context.Context, s map[string]*schema.Schema, d *schema.ResourceData, m interface{}) diag.Diagnostics { ethernetInterface, err := findInterfaceByDefaultName(s, d, m.(Client)) if err != nil {