From 0ee5256d4bda0abdbde53808e774c28159dfe8da Mon Sep 17 00:00:00 2001 From: Felix Date: Thu, 11 Jan 2024 22:06:28 +0300 Subject: [PATCH 1/7] feat(switch): Add support for /interface/ethernet/switch/port Fixes #325 --- routeros/provider.go | 41 ++--- ...resource_interface_ethernet_switch_port.go | 153 ++++++++++++++++++ ...rce_interface_ethernet_switch_port_test.go | 9 ++ 3 files changed, 183 insertions(+), 20 deletions(-) create mode 100644 routeros/resource_interface_ethernet_switch_port.go create mode 100644 routeros/resource_interface_ethernet_switch_port_test.go diff --git a/routeros/provider.go b/routeros/provider.go index 4ed2fb0c..f0a6f6bc 100644 --- a/routeros/provider.go +++ b/routeros/provider.go @@ -100,26 +100,27 @@ func Provider() *schema.Provider { "routeros_dns_record": ResourceDnsRecord(), // Interface Objects - "routeros_interface_bridge": ResourceInterfaceBridge(), - "routeros_interface_bridge_port": ResourceInterfaceBridgePort(), - "routeros_interface_bridge_vlan": ResourceInterfaceBridgeVlan(), - "routeros_interface_bridge_settings": ResourceInterfaceBridgeSettings(), - "routeros_interface_dot1x_client": ResourceInterfaceDot1xClient(), - "routeros_interface_dot1x_server": ResourceInterfaceDot1xServer(), - "routeros_interface_eoip": ResourceInterfaceEoip(), - "routeros_interface_ethernet_switch": ResourceInterfaceEthernetSwitch(), - "routeros_interface_gre": ResourceInterfaceGre(), - "routeros_interface_vlan": ResourceInterfaceVlan(), - "routeros_interface_vrrp": ResourceInterfaceVrrp(), - "routeros_interface_wireguard": ResourceInterfaceWireguard(), - "routeros_interface_wireguard_peer": ResourceInterfaceWireguardPeer(), - "routeros_interface_list": ResourceInterfaceList(), - "routeros_interface_list_member": ResourceInterfaceListMember(), - "routeros_interface_ovpn_server": ResourceInterfaceOpenVPNServer(), - "routeros_interface_veth": ResourceInterfaceVeth(), - "routeros_interface_bonding": ResourceInterfaceBonding(), - "routeros_interface_pppoe_client": ResourceInterfacePPPoEClient(), - "routeros_interface_ethernet": ResourceInterfaceEthernet(), + "routeros_interface_bridge": ResourceInterfaceBridge(), + "routeros_interface_bridge_port": ResourceInterfaceBridgePort(), + "routeros_interface_bridge_vlan": ResourceInterfaceBridgeVlan(), + "routeros_interface_bridge_settings": ResourceInterfaceBridgeSettings(), + "routeros_interface_dot1x_client": ResourceInterfaceDot1xClient(), + "routeros_interface_dot1x_server": ResourceInterfaceDot1xServer(), + "routeros_interface_eoip": ResourceInterfaceEoip(), + "routeros_interface_ethernet_switch": ResourceInterfaceEthernetSwitch(), + "routeros_interface_ethernet_switch_port": ResourceInterfaceEthernetSwitchPort(), + "routeros_interface_gre": ResourceInterfaceGre(), + "routeros_interface_vlan": ResourceInterfaceVlan(), + "routeros_interface_vrrp": ResourceInterfaceVrrp(), + "routeros_interface_wireguard": ResourceInterfaceWireguard(), + "routeros_interface_wireguard_peer": ResourceInterfaceWireguardPeer(), + "routeros_interface_list": ResourceInterfaceList(), + "routeros_interface_list_member": ResourceInterfaceListMember(), + "routeros_interface_ovpn_server": ResourceInterfaceOpenVPNServer(), + "routeros_interface_veth": ResourceInterfaceVeth(), + "routeros_interface_bonding": ResourceInterfaceBonding(), + "routeros_interface_pppoe_client": ResourceInterfacePPPoEClient(), + "routeros_interface_ethernet": ResourceInterfaceEthernet(), // Aliases for interface objects to retain compatibility between original and fork "routeros_bridge": ResourceInterfaceBridge(), diff --git a/routeros/resource_interface_ethernet_switch_port.go b/routeros/resource_interface_ethernet_switch_port.go new file mode 100644 index 00000000..fc38e018 --- /dev/null +++ b/routeros/resource_interface_ethernet_switch_port.go @@ -0,0 +1,153 @@ +package routeros + +import ( + "context" + "fmt" + "regexp" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +/* + { + ".id": "*1000000", + "default-vlan-id": "0", + "invalid": "false", + "name": "switch1-cpu", + "rx-1024-1518": "0", + "rx-128-255": "0", + "rx-1519-max": "0", + "rx-256-511": "0", + "rx-512-1023": "0", + "rx-64": "0", + "rx-65-127": "0", + "rx-align-error": "0", + "rx-broadcast": "0", + "rx-bytes": "0", + "rx-fcs-error": "0", + "rx-fragment": "0", + "rx-multicast": "0", + "rx-overflow": "0", + "rx-pause": "0", + "rx-too-long": "0", + "rx-too-short": "0", + "switch": "switch1", + "tx-1024-1518": "0", + "tx-128-255": "0", + "tx-1519-max": "0", + "tx-256-511": "0", + "tx-512-1023": "0", + "tx-64": "0", + "tx-65-127": "0", + "tx-broadcast": "0", + "tx-bytes": "0", + "tx-collision": "0", + "tx-deferred": "0", + "tx-excessive-collision": "0", + "tx-excessive-deferred": "0", + "tx-late-collision": "0", + "tx-multicast": "0", + "tx-multiple-collision": "0", + "tx-pause": "0", + "tx-single-collision": "0", + "tx-too-long": "0", + "tx-underrun": "0", + "vlan-header": "leave-as-is", + "vlan-mode": "disabled" + } +*/ + +// https://help.mikrotik.com/docs/display/ROS/L3+Hardware+Offloading#L3HardwareOffloading-SwitchPortConfiguration +// https://help.mikrotik.com/docs/display/ROS/Switch+Chip+Features +func ResourceInterfaceEthernetSwitchPort() *schema.Resource { + resSchema := map[string]*schema.Schema{ + MetaResourcePath: PropResourcePath("/interface/ethernet/switch/port"), + MetaId: PropId(Id), + MetaSkipFields: PropSkipFields(`"name","rx_1024_1518","rx_128_255","rx_1519_max","rx_256_511","rx_512_1023","rx_64",` + + `"rx_65_127","rx_align_error","rx_broadcast","rx_bytes","rx_fcs_error","rx_fragment","rx_multicast","rx_overflow",` + + `"rx_pause","rx_too_long","rx_too_short","tx_1024_1518","tx_128_255","tx_1519_max","tx_256_511","tx_512_1023","tx_64",` + + `"tx_65_127","tx_broadcast","tx_bytes","tx_collision","tx_deferred","tx_excessive_collision","tx_excessive_deferred",` + + `"tx_late_collision","tx_multicast","tx_multiple_collision","tx_pause","tx_single_collision","tx_too_long","tx_underrun",` + + `"driver_tx_byte","driver_rx_packet","driver_rx_byte","driver_tx_packet"`), + + "default_vlan_id": { + Type: schema.TypeString, + Optional: true, + Description: "Adds a VLAN tag with the specified VLAN ID on all untagged ingress traffic on a port, should be used " + + "with ```vlan-header``` set to ```always-strip``` on a port to configure the port to be the access port. For hybrid ports " + + "```default-vlan-id``` is used to tag untagged traffic. If two ports have the same ```default-vlan-id```, then VLAN tag is " + + "not added since the switch chip assumes that traffic is being forwarded between access ports.", + ValidateFunc: validation.StringMatch(regexp.MustCompile(`auto|\d+`), `Value must be "auto" or integer: 0..4095`), + DiffSuppressFunc: AlwaysPresentNotUserProvided, + }, + KeyInvalid: PropInvalidRo, + KeyName: PropName("Port name."), + "switch": { + Type: schema.TypeString, + Computed: true, + Description: "Name of the switch.", + }, + "vlan_header": { + Type: schema.TypeString, + Optional: true, + Description: "Sets action which is performed on the port for egress traffic.", + ValidateFunc: validation.StringInSlice([]string{"add-if-missing", "always-strip", "leave-as-is"}, false), + DiffSuppressFunc: AlwaysPresentNotUserProvided, + }, + "vlan_mode": { + Type: schema.TypeString, + Optional: true, + Description: "Changes the VLAN lookup mechanism against the VLAN Table for ingress traffic.", + ValidateFunc: validation.StringInSlice([]string{"check", "disabled", "fallback", "secure"}, false), + DiffSuppressFunc: AlwaysPresentNotUserProvided, + }, + } + + resCreateUpdate := func(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + item, metadata := TerraformResourceDataToMikrotik(resSchema, d) + + res, err := ReadItems(&ItemId{Name, d.Get("name").(string)}, metadata.Path, m.(Client)) + if err != nil { + // API/REST client error. + ColorizedDebug(ctx, fmt.Sprintf(ErrorMsgPatch, err)) + return diag.FromErr(err) + } + + // Resource not found. + if len(*res) == 0 { + d.SetId("") + ColorizedDebug(ctx, fmt.Sprintf(ErrorMsgPatch, err)) + return diag.FromErr(errorNoLongerExists) + } + + d.SetId((*res)[0].GetID(Id)) + item[".id"] = d.Id() + + var resUrl string + if m.(Client).GetTransport() == TransportREST { + resUrl = "/set" + } + + err = m.(Client).SendRequest(crudPost, &URL{Path: metadata.Path + resUrl}, item, nil) + if err != nil { + return diag.FromErr(err) + } + + return ResourceRead(ctx, resSchema, d, m) + } + + return &schema.Resource{ + CreateContext: resCreateUpdate, + ReadContext: DefaultRead(resSchema), + UpdateContext: resCreateUpdate, + DeleteContext: DefaultSystemDelete(resSchema), + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: resSchema, + } +} diff --git a/routeros/resource_interface_ethernet_switch_port_test.go b/routeros/resource_interface_ethernet_switch_port_test.go new file mode 100644 index 00000000..12933279 --- /dev/null +++ b/routeros/resource_interface_ethernet_switch_port_test.go @@ -0,0 +1,9 @@ +package routeros + +import ( + "testing" +) + +func TestAccInterfaceEthernetSwitchPortTest_basic(t *testing.T) { + t.Log("Test skipped, The test is skipped, the resource is only available on real hardware.") +} From d0cdb7ea276e9d5c664b48d2e430bd33a30e7e47 Mon Sep 17 00:00:00 2001 From: Felix Date: Thu, 11 Jan 2024 23:03:22 +0300 Subject: [PATCH 2/7] feat(switch) Add support for /interface/ethernet/switch/port-isolation --- routeros/provider.go | 43 ++++----- ...nterface_ethernet_switch_port_isolation.go | 88 +++++++++++++++++++ ...ace_ethernet_switch_port_isolation_test.go | 9 ++ 3 files changed, 119 insertions(+), 21 deletions(-) create mode 100644 routeros/resource_interface_ethernet_switch_port_isolation.go create mode 100644 routeros/resource_interface_ethernet_switch_port_isolation_test.go diff --git a/routeros/provider.go b/routeros/provider.go index f0a6f6bc..425cf5cc 100644 --- a/routeros/provider.go +++ b/routeros/provider.go @@ -100,27 +100,28 @@ func Provider() *schema.Provider { "routeros_dns_record": ResourceDnsRecord(), // Interface Objects - "routeros_interface_bridge": ResourceInterfaceBridge(), - "routeros_interface_bridge_port": ResourceInterfaceBridgePort(), - "routeros_interface_bridge_vlan": ResourceInterfaceBridgeVlan(), - "routeros_interface_bridge_settings": ResourceInterfaceBridgeSettings(), - "routeros_interface_dot1x_client": ResourceInterfaceDot1xClient(), - "routeros_interface_dot1x_server": ResourceInterfaceDot1xServer(), - "routeros_interface_eoip": ResourceInterfaceEoip(), - "routeros_interface_ethernet_switch": ResourceInterfaceEthernetSwitch(), - "routeros_interface_ethernet_switch_port": ResourceInterfaceEthernetSwitchPort(), - "routeros_interface_gre": ResourceInterfaceGre(), - "routeros_interface_vlan": ResourceInterfaceVlan(), - "routeros_interface_vrrp": ResourceInterfaceVrrp(), - "routeros_interface_wireguard": ResourceInterfaceWireguard(), - "routeros_interface_wireguard_peer": ResourceInterfaceWireguardPeer(), - "routeros_interface_list": ResourceInterfaceList(), - "routeros_interface_list_member": ResourceInterfaceListMember(), - "routeros_interface_ovpn_server": ResourceInterfaceOpenVPNServer(), - "routeros_interface_veth": ResourceInterfaceVeth(), - "routeros_interface_bonding": ResourceInterfaceBonding(), - "routeros_interface_pppoe_client": ResourceInterfacePPPoEClient(), - "routeros_interface_ethernet": ResourceInterfaceEthernet(), + "routeros_interface_bridge": ResourceInterfaceBridge(), + "routeros_interface_bridge_port": ResourceInterfaceBridgePort(), + "routeros_interface_bridge_vlan": ResourceInterfaceBridgeVlan(), + "routeros_interface_bridge_settings": ResourceInterfaceBridgeSettings(), + "routeros_interface_dot1x_client": ResourceInterfaceDot1xClient(), + "routeros_interface_dot1x_server": ResourceInterfaceDot1xServer(), + "routeros_interface_eoip": ResourceInterfaceEoip(), + "routeros_interface_ethernet_switch": ResourceInterfaceEthernetSwitch(), + "routeros_interface_ethernet_switch_port": ResourceInterfaceEthernetSwitchPort(), + "routeros_interface_ethernet_switch_port_isolation": ResourceInterfaceEthernetSwitchPortIsolation(), + "routeros_interface_gre": ResourceInterfaceGre(), + "routeros_interface_vlan": ResourceInterfaceVlan(), + "routeros_interface_vrrp": ResourceInterfaceVrrp(), + "routeros_interface_wireguard": ResourceInterfaceWireguard(), + "routeros_interface_wireguard_peer": ResourceInterfaceWireguardPeer(), + "routeros_interface_list": ResourceInterfaceList(), + "routeros_interface_list_member": ResourceInterfaceListMember(), + "routeros_interface_ovpn_server": ResourceInterfaceOpenVPNServer(), + "routeros_interface_veth": ResourceInterfaceVeth(), + "routeros_interface_bonding": ResourceInterfaceBonding(), + "routeros_interface_pppoe_client": ResourceInterfacePPPoEClient(), + "routeros_interface_ethernet": ResourceInterfaceEthernet(), // Aliases for interface objects to retain compatibility between original and fork "routeros_bridge": ResourceInterfaceBridge(), diff --git a/routeros/resource_interface_ethernet_switch_port_isolation.go b/routeros/resource_interface_ethernet_switch_port_isolation.go new file mode 100644 index 00000000..71afd8e8 --- /dev/null +++ b/routeros/resource_interface_ethernet_switch_port_isolation.go @@ -0,0 +1,88 @@ +package routeros + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +/* +{ + ".id": "*1", + "forwarding-override": "ether1", + "invalid": "false", + "name": "ether1", + "switch": "switch1" +} +*/ + +// https://help.mikrotik.com/docs/display/ROS/Switch+Chip+Features +func ResourceInterfaceEthernetSwitchPortIsolation() *schema.Resource { + resSchema := map[string]*schema.Schema{ + MetaResourcePath: PropResourcePath("/interface/ethernet/switch/port-isolation"), + MetaId: PropId(Id), + MetaSkipFields: PropSkipFields(`"name"`), + MetaSetUnsetFields: PropSetUnsetFields(`"forwarding_override"`), + + KeyInvalid: PropInvalidRo, + KeyName: PropName("Port name."), + "switch": { + Type: schema.TypeString, + Computed: true, + Description: "Name of the switch.", + }, + "forwarding_override": { + Type: schema.TypeString, + Optional: true, + Description: "Forces ingress traffic to be forwarded to a specific interface. Multiple interfaces can be specified by separating them with a comma.", + }, + } + + resCreateUpdate := func(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + item, metadata := TerraformResourceDataToMikrotik(resSchema, d) + + res, err := ReadItems(&ItemId{Name, d.Get("name").(string)}, metadata.Path, m.(Client)) + if err != nil { + // API/REST client error. + ColorizedDebug(ctx, fmt.Sprintf(ErrorMsgPatch, err)) + return diag.FromErr(err) + } + + // Resource not found. + if len(*res) == 0 { + d.SetId("") + ColorizedDebug(ctx, fmt.Sprintf(ErrorMsgPatch, err)) + return diag.FromErr(errorNoLongerExists) + } + + d.SetId((*res)[0].GetID(Id)) + item[".id"] = d.Id() + + var resUrl string + if m.(Client).GetTransport() == TransportREST { + resUrl = "/set" + } + + err = m.(Client).SendRequest(crudPost, &URL{Path: metadata.Path + resUrl}, item, nil) + if err != nil { + return diag.FromErr(err) + } + + return ResourceRead(ctx, resSchema, d, m) + } + + return &schema.Resource{ + CreateContext: resCreateUpdate, + ReadContext: DefaultRead(resSchema), + UpdateContext: resCreateUpdate, + DeleteContext: DefaultSystemDelete(resSchema), + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: resSchema, + } +} diff --git a/routeros/resource_interface_ethernet_switch_port_isolation_test.go b/routeros/resource_interface_ethernet_switch_port_isolation_test.go new file mode 100644 index 00000000..b3b8cb76 --- /dev/null +++ b/routeros/resource_interface_ethernet_switch_port_isolation_test.go @@ -0,0 +1,9 @@ +package routeros + +import ( + "testing" +) + +func TestAccInterfaceEthernetSwitchPortIsolationTest_basic(t *testing.T) { + t.Log("Test skipped, The test is skipped, the resource is only available on real hardware.") +} From 1613f5a63098554d95880958be504dd17d4f569b Mon Sep 17 00:00:00 2001 From: Felix Date: Fri, 12 Jan 2024 20:29:20 +0300 Subject: [PATCH 3/7] feat(switch): Add support for /interface/ethernet/switch/host Fixes #325 --- routeros/provider.go | 1 + routeros/provider_schema_helpers.go | 21 ++++ ...resource_interface_ethernet_switch_host.go | 98 +++++++++++++++++++ ...rce_interface_ethernet_switch_host_test.go | 9 ++ ...nterface_ethernet_switch_port_isolation.go | 2 +- 5 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 routeros/resource_interface_ethernet_switch_host.go create mode 100644 routeros/resource_interface_ethernet_switch_host_test.go diff --git a/routeros/provider.go b/routeros/provider.go index 425cf5cc..afb7ac83 100644 --- a/routeros/provider.go +++ b/routeros/provider.go @@ -108,6 +108,7 @@ func Provider() *schema.Provider { "routeros_interface_dot1x_server": ResourceInterfaceDot1xServer(), "routeros_interface_eoip": ResourceInterfaceEoip(), "routeros_interface_ethernet_switch": ResourceInterfaceEthernetSwitch(), + "routeros_interface_ethernet_switch_host": ResourceInterfaceEthernetSwitchHost(), "routeros_interface_ethernet_switch_port": ResourceInterfaceEthernetSwitchPort(), "routeros_interface_ethernet_switch_port_isolation": ResourceInterfaceEthernetSwitchPortIsolation(), "routeros_interface_gre": ResourceInterfaceGre(), diff --git a/routeros/provider_schema_helpers.go b/routeros/provider_schema_helpers.go index 980f8f47..9685271c 100644 --- a/routeros/provider_schema_helpers.go +++ b/routeros/provider_schema_helpers.go @@ -127,6 +127,27 @@ func PropName(description string) *schema.Schema { } } +// PropMacAddress +func PropMacAddressRw(description string, required bool) *schema.Schema { + mac := &schema.Schema{ + Type: schema.TypeString, + Description: description, + ValidateFunc: validation.IsMACAddress, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + if old != "" && d.GetRawConfig().GetAttr(k).IsNull() { + return true + } + return strings.EqualFold(old, new) + }, + } + if required { + mac.Required = true + } else { + mac.Optional = true + } + return mac +} + // Schema properties. var ( PropActualMtuRo = &schema.Schema{ diff --git a/routeros/resource_interface_ethernet_switch_host.go b/routeros/resource_interface_ethernet_switch_host.go new file mode 100644 index 00000000..c64db138 --- /dev/null +++ b/routeros/resource_interface_ethernet_switch_host.go @@ -0,0 +1,98 @@ +package routeros + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +/* +{ + ".id": "*1", + "copy-to-cpu": "false", + "drop": "false", + "dynamic": "false", + "invalid": "false", + "mac-address": "00:00:00:00:00:00", + "mirror": "false", + "ports": "ether1", + "redirect-to-cpu": "false", + "switch": "switch1" +} +*/ + +// https://help.mikrotik.com/docs/display/ROS/Switch+Chip+Features#SwitchChipFeatures-HostTable +func ResourceInterfaceEthernetSwitchHost() *schema.Resource { + resSchema := map[string]*schema.Schema{ + MetaResourcePath: PropResourcePath("/interface/ethernet/switch/host"), + MetaId: PropId(Id), + + "copy_to_cpu": { + Type: schema.TypeBool, + Optional: true, + Description: "Whether to send a frame copy to switch CPU port from a frame with matching MAC destination address " + + "(matching destination or source address for CRS3xx series switches).", + }, + "drop": { + Type: schema.TypeBool, + Optional: true, + Description: "Whether to drop a frame with matching MAC source address received on a certain port (matching "+ + "destination or source address for CRS3xx series switches).", + }, + KeyDynamic: PropDynamicRo, + KeyInvalid: PropInvalidRo, + KeyMacAddress: PropMacAddressRw("Host's MAC address.", true), + "mirror": { + Type: schema.TypeBool, + Optional: true, + Description: "Whether to send a frame copy to mirror-target port from a frame with matching MAC destination address "+ + "(matching destination or source address for CRS3xx series switches).", + }, + "ports": { + Type: schema.TypeList, + Required: true, + Description: "Name of the interface, static MAC address can be mapped to more that one port, including switch CPU port.", + Elem: &schema.Schema{ + Type: schema.TypeString, + DiffSuppressFunc: AlwaysPresentNotUserProvided, + }, + }, + "redirect_to_cpu": { + Type: schema.TypeBool, + Optional: true, + Description: "Whether to redirect a frame to switch CPU port from a frame with matching MAC destination address "+ + "(matching destination or source address for CRS3xx series switches).", + }, + "share_vlan_learned": { + Type: schema.TypeBool, + Optional: true, + Description: "Whether the static host MAC address lookup is used with shared-VLAN-learning (SVL) or "+ + "independent-VLAN-learning (IVL). The SVL mode is used for those VLAN entries that do not support IVL or IVL is "+ + "disabled (independent-learning=no).", + }, + "switch": { + Type: schema.TypeString, + Required: true, + Description: "Name of the switch to which the MAC address is going to be assigned to.", + }, + "vlan_id": { + Type: schema.TypeString, + Optional: true, + Description: "VLAN ID for the statically added MAC address entry.", + ValidateFunc: validation.IntBetween(0, 4094), + DiffSuppressFunc: AlwaysPresentNotUserProvided, + }, + } + + return &schema.Resource{ + CreateContext: DefaultCreate(resSchema), + ReadContext: DefaultRead(resSchema), + UpdateContext: DefaultUpdate(resSchema), + DeleteContext: DefaultSystemDelete(resSchema), + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: resSchema, + } +} diff --git a/routeros/resource_interface_ethernet_switch_host_test.go b/routeros/resource_interface_ethernet_switch_host_test.go new file mode 100644 index 00000000..e0d9a4b2 --- /dev/null +++ b/routeros/resource_interface_ethernet_switch_host_test.go @@ -0,0 +1,9 @@ +package routeros + +import ( + "testing" +) + +func TestAccInterfaceEthernetSwitchHost_basic(t *testing.T) { + t.Log("Test skipped, The test is skipped, the resource is only available on real hardware.") +} \ No newline at end of file diff --git a/routeros/resource_interface_ethernet_switch_port_isolation.go b/routeros/resource_interface_ethernet_switch_port_isolation.go index 71afd8e8..84f74797 100644 --- a/routeros/resource_interface_ethernet_switch_port_isolation.go +++ b/routeros/resource_interface_ethernet_switch_port_isolation.go @@ -18,7 +18,7 @@ import ( } */ -// https://help.mikrotik.com/docs/display/ROS/Switch+Chip+Features +// https://help.mikrotik.com/docs/display/ROS/Switch+Chip+Features#SwitchChipFeatures-Portisolation func ResourceInterfaceEthernetSwitchPortIsolation() *schema.Resource { resSchema := map[string]*schema.Schema{ MetaResourcePath: PropResourcePath("/interface/ethernet/switch/port-isolation"), From 7463f0e272e38caffab345f2bd7ae4f8a0a0fba9 Mon Sep 17 00:00:00 2001 From: Felix Date: Fri, 12 Jan 2024 20:44:35 +0300 Subject: [PATCH 4/7] feat(switch): Add support for /interface/ethernet/switch/vlan Fixes #325 --- routeros/provider.go | 3 +- ...resource_interface_ethernet_switch_vlan.go | 69 +++++++++++++++++++ ...rce_interface_ethernet_switch_vlan_test.go | 9 +++ 3 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 routeros/resource_interface_ethernet_switch_vlan.go create mode 100644 routeros/resource_interface_ethernet_switch_vlan_test.go diff --git a/routeros/provider.go b/routeros/provider.go index afb7ac83..f9d81d9d 100644 --- a/routeros/provider.go +++ b/routeros/provider.go @@ -108,9 +108,10 @@ func Provider() *schema.Provider { "routeros_interface_dot1x_server": ResourceInterfaceDot1xServer(), "routeros_interface_eoip": ResourceInterfaceEoip(), "routeros_interface_ethernet_switch": ResourceInterfaceEthernetSwitch(), - "routeros_interface_ethernet_switch_host": ResourceInterfaceEthernetSwitchHost(), + "routeros_interface_ethernet_switch_host": ResourceInterfaceEthernetSwitchHost(), "routeros_interface_ethernet_switch_port": ResourceInterfaceEthernetSwitchPort(), "routeros_interface_ethernet_switch_port_isolation": ResourceInterfaceEthernetSwitchPortIsolation(), + "routeros_interface_ethernet_switch_vlan": ResourceInterfaceEthernetSwitchVlan(), "routeros_interface_gre": ResourceInterfaceGre(), "routeros_interface_vlan": ResourceInterfaceVlan(), "routeros_interface_vrrp": ResourceInterfaceVrrp(), diff --git a/routeros/resource_interface_ethernet_switch_vlan.go b/routeros/resource_interface_ethernet_switch_vlan.go new file mode 100644 index 00000000..97253618 --- /dev/null +++ b/routeros/resource_interface_ethernet_switch_vlan.go @@ -0,0 +1,69 @@ +package routeros + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +/* +{ + ".id": "*2", + "disabled": "false", + "invalid": "false", + "ports": "ether1", + "switch": "switch1", + "vlan-id": "0" +} +*/ + +// https://help.mikrotik.com/docs/display/ROS/Switch+Chip+Features#SwitchChipFeatures-VLANTable +func ResourceInterfaceEthernetSwitchVlan() *schema.Resource { + resSchema := map[string]*schema.Schema{ + MetaResourcePath: PropResourcePath("/interface/ethernet/switch/vlan"), + MetaId: PropId(Id), + + KeyComment: PropCommentRw, + KeyDisabled: PropDisabledRw, + "independent_learning": { + Type: schema.TypeBool, + Optional: true, + Description: "Whether to use shared-VLAN-learning (SVL) or independent-VLAN-learning (IVL).", + DiffSuppressFunc: AlwaysPresentNotUserProvided, + }, + KeyInvalid: PropInvalidRo, + "ports": { + Type: schema.TypeList, + Required: true, + Description: "Interface member list for the respective VLAN.", + Elem: &schema.Schema{ + Type: schema.TypeString, + DiffSuppressFunc: AlwaysPresentNotUserProvided, + }, + }, + "switch": { + Type: schema.TypeString, + Required: true, + Description: "Name of the switch for which the respective VLAN entry is intended for.", + }, + "vlan_id": { + Type: schema.TypeInt, + Optional: true, + Description: "The VLAN ID for certain switch port configurations.", + ValidateFunc: validation.IntBetween(0, 4094), + DiffSuppressFunc: AlwaysPresentNotUserProvided, + }, + } + + return &schema.Resource{ + CreateContext: DefaultCreate(resSchema), + ReadContext: DefaultRead(resSchema), + UpdateContext: DefaultUpdate(resSchema), + DeleteContext: DefaultSystemDelete(resSchema), + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: resSchema, + } +} diff --git a/routeros/resource_interface_ethernet_switch_vlan_test.go b/routeros/resource_interface_ethernet_switch_vlan_test.go new file mode 100644 index 00000000..c0d2602e --- /dev/null +++ b/routeros/resource_interface_ethernet_switch_vlan_test.go @@ -0,0 +1,9 @@ +package routeros + +import ( + "testing" +) + +func TestAccInterfaceEthernetSwitchVlan_basic(t *testing.T) { + t.Log("Test skipped, The test is skipped, the resource is only available on real hardware.") +} From a14c2265d05968898965ea47e2968df6b3611edd Mon Sep 17 00:00:00 2001 From: Felix Date: Fri, 12 Jan 2024 20:46:27 +0300 Subject: [PATCH 5/7] chore: go fmt --- routeros/provider_schema_helpers.go | 6 +-- routeros/resource_capsman_access_list.go | 32 +++++------ .../resource_interface_bridge_mlag _test.go | 2 +- routeros/resource_interface_dot1x.go | 54 +++++++++---------- ...resource_interface_ethernet_switch_host.go | 34 ++++++------ ...rce_interface_ethernet_switch_host_test.go | 2 +- ...resource_interface_ethernet_switch_test.go | 2 +- routeros/resource_ip_dhcp_server_config.go | 24 ++++----- routeros/resource_ovpn_server.go | 2 +- routeros/resource_radius.go | 44 +++++++-------- routeros/resource_system_logging.go | 4 +- 11 files changed, 103 insertions(+), 103 deletions(-) diff --git a/routeros/provider_schema_helpers.go b/routeros/provider_schema_helpers.go index 9685271c..acae7ad0 100644 --- a/routeros/provider_schema_helpers.go +++ b/routeros/provider_schema_helpers.go @@ -130,9 +130,9 @@ func PropName(description string) *schema.Schema { // PropMacAddress func PropMacAddressRw(description string, required bool) *schema.Schema { mac := &schema.Schema{ - Type: schema.TypeString, - Description: description, - ValidateFunc: validation.IsMACAddress, + Type: schema.TypeString, + Description: description, + ValidateFunc: validation.IsMACAddress, DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { if old != "" && d.GetRawConfig().GetAttr(k).IsNull() { return true diff --git a/routeros/resource_capsman_access_list.go b/routeros/resource_capsman_access_list.go index 12a4a0e1..0169b913 100644 --- a/routeros/resource_capsman_access_list.go +++ b/routeros/resource_capsman_access_list.go @@ -29,19 +29,19 @@ func ResourceCapsManAccessList() *schema.Resource { MetaResourcePath: PropResourcePath("/caps-man/access-list"), MetaId: PropId(Id), - KeyComment: PropCommentRw, + KeyComment: PropCommentRw, KeyDisabled: PropDisabledRw, "action": { - Type: schema.TypeString, - Optional: true, - Description: "An action to take when a client matches.", + Type: schema.TypeString, + Optional: true, + Description: "An action to take when a client matches.", ValidateFunc: validation.StringInSlice([]string{"accept", "reject", "query-radius"}, false), }, "allow_signal_out_of_range": { - Type: schema.TypeString, - Optional: true, - Default: "10s", - Description: "An option that permits the client's signal to be out of the range always or for some time interval.", + Type: schema.TypeString, + Optional: true, + Default: "10s", + Description: "An option that permits the client's signal to be out of the range always or for some time interval.", DiffSuppressFunc: TimeEquall, }, "ap_tx_limit": { @@ -70,8 +70,8 @@ func ResourceCapsManAccessList() *schema.Resource { Description: "MAC address mask to apply when comparing clients' addresses.", }, "interface": { - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Optional: true, Description: "Interface name to compare with an interface to which the client actually connects to.", }, KeyPlaceBefore: PropPlaceBefore, @@ -92,14 +92,14 @@ func ResourceCapsManAccessList() *schema.Resource { Description: "The range in which the client signal must fall.", }, "ssid_regexp": { - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Optional: true, Description: "The regular expression to compare the actual SSID the client connects to.", }, "time": { Type: schema.TypeString, Optional: true, - Default: "0s-1d,sun,mon,tue,wed,thu,fri,sat", + Default: "0s-1d,sun,mon,tue,wed,thu,fri,sat", Description: "Time of the day and days of the week when the rule is applicable.", }, "vlan_id": { @@ -109,9 +109,9 @@ func ResourceCapsManAccessList() *schema.Resource { ValidateFunc: validation.IntBetween(1, 4094), }, "vlan_mode": { - Type: schema.TypeString, - Optional: true, - Description: "VLAN tagging mode specifies if traffic coming from a client should get tagged and untagged when it goes back to the client.", + Type: schema.TypeString, + Optional: true, + Description: "VLAN tagging mode specifies if traffic coming from a client should get tagged and untagged when it goes back to the client.", ValidateFunc: validation.StringInSlice([]string{"no-tag", "use-service-tag", "use-tag"}, false), }, } diff --git a/routeros/resource_interface_bridge_mlag _test.go b/routeros/resource_interface_bridge_mlag _test.go index 81719388..1ad75758 100644 --- a/routeros/resource_interface_bridge_mlag _test.go +++ b/routeros/resource_interface_bridge_mlag _test.go @@ -6,4 +6,4 @@ import ( func TestAccInterfaceBridgeMlagTest_basic(t *testing.T) { t.Log("Test skipped, The test is skipped, the resource is only available on real hardware.") -} \ No newline at end of file +} diff --git a/routeros/resource_interface_dot1x.go b/routeros/resource_interface_dot1x.go index d8ccd128..b0f89c21 100644 --- a/routeros/resource_interface_dot1x.go +++ b/routeros/resource_interface_dot1x.go @@ -49,7 +49,7 @@ func ResourceInterfaceDot1xClient() *schema.Resource { return &schema.Resource{ CreateContext: DefaultCreate(resSchema), - ReadContext: DefaultRead(resSchema), + ReadContext: DefaultRead(resSchema), UpdateContext: DefaultUpdate(resSchema), DeleteContext: DefaultDelete(resSchema), @@ -74,10 +74,10 @@ func ResourceInterfaceDot1xServer() *schema.Resource { Description: "Whether to send RADIUS accounting requests to the authentication server.", }, "auth_timeout": { - Type: schema.TypeString, - Optional: true, - Default: "1m", - Description: "Total time available for EAP authentication.", + Type: schema.TypeString, + Optional: true, + Default: "1m", + Description: "Total time available for EAP authentication.", DiffSuppressFunc: TimeEquall, }, "auth_types": { @@ -96,17 +96,17 @@ func ResourceInterfaceDot1xServer() *schema.Resource { }, KeyInterface: PropInterfaceRw, "interim_update": { - Type: schema.TypeString, - Optional: true, - Default: "0s", - Description: "Interval between scheduled RADIUS Interim-Update messages.", + Type: schema.TypeString, + Optional: true, + Default: "0s", + Description: "Interval between scheduled RADIUS Interim-Update messages.", DiffSuppressFunc: TimeEquall, }, "mac_auth_mode": { - Type: schema.TypeString, - Optional: true, - Default: "mac-as-username", - Description: "An option that allows to control User-Name and User-Password RADIUS attributes when using MAC authentication.", + Type: schema.TypeString, + Optional: true, + Default: "mac-as-username", + Description: "An option that allows to control User-Name and User-Password RADIUS attributes when using MAC authentication.", ValidateFunc: validation.StringInSlice([]string{"mac-as-username", "mac-as-username-and-password"}, false), }, "radius_mac_format": { @@ -118,35 +118,35 @@ func ResourceInterfaceDot1xServer() *schema.Resource { "xx-xx-xx-xx-xx-xx", "xx:xx:xx:xx:xx:xx", "xxxxxxxxxxxx"}, false), }, "reauth_timeout": { - Type: schema.TypeString, - Optional: true, - Description: "An option that enables server port re-authentication.", + Type: schema.TypeString, + Optional: true, + Description: "An option that enables server port re-authentication.", DiffSuppressFunc: TimeEquall, }, "reject_vlan_id": { - Type: schema.TypeInt, - Optional: true, - Description: "Assigned VLAN when authentication failed, and a RADIUS server responded with an Access-Reject message. ", + Type: schema.TypeInt, + Optional: true, + Description: "Assigned VLAN when authentication failed, and a RADIUS server responded with an Access-Reject message. ", ValidateFunc: validation.IntBetween(1, 4094), }, "retrans_timeout": { - Type: schema.TypeString, - Optional: true, - Default: "30s", - Description: "The time interval between message re-transmissions if no response is received from the supplicant.", + Type: schema.TypeString, + Optional: true, + Default: "30s", + Description: "The time interval between message re-transmissions if no response is received from the supplicant.", DiffSuppressFunc: TimeEquall, }, "server_fail_vlan_id": { - Type: schema.TypeInt, - Optional: true, - Description: "Assigned VLAN when RADIUS server is not responding and request timed out.", + Type: schema.TypeInt, + Optional: true, + Description: "Assigned VLAN when RADIUS server is not responding and request timed out.", ValidateFunc: validation.IntBetween(1, 4094), }, } return &schema.Resource{ CreateContext: DefaultCreate(resSchema), - ReadContext: DefaultRead(resSchema), + ReadContext: DefaultRead(resSchema), UpdateContext: DefaultUpdate(resSchema), DeleteContext: DefaultDelete(resSchema), diff --git a/routeros/resource_interface_ethernet_switch_host.go b/routeros/resource_interface_ethernet_switch_host.go index c64db138..17b812b0 100644 --- a/routeros/resource_interface_ethernet_switch_host.go +++ b/routeros/resource_interface_ethernet_switch_host.go @@ -35,39 +35,39 @@ func ResourceInterfaceEthernetSwitchHost() *schema.Resource { "drop": { Type: schema.TypeBool, Optional: true, - Description: "Whether to drop a frame with matching MAC source address received on a certain port (matching "+ - "destination or source address for CRS3xx series switches).", + Description: "Whether to drop a frame with matching MAC source address received on a certain port (matching " + + "destination or source address for CRS3xx series switches).", }, - KeyDynamic: PropDynamicRo, - KeyInvalid: PropInvalidRo, + KeyDynamic: PropDynamicRo, + KeyInvalid: PropInvalidRo, KeyMacAddress: PropMacAddressRw("Host's MAC address.", true), "mirror": { Type: schema.TypeBool, Optional: true, - Description: "Whether to send a frame copy to mirror-target port from a frame with matching MAC destination address "+ - "(matching destination or source address for CRS3xx series switches).", + Description: "Whether to send a frame copy to mirror-target port from a frame with matching MAC destination address " + + "(matching destination or source address for CRS3xx series switches).", }, "ports": { - Type: schema.TypeList, - Required: true, + Type: schema.TypeList, + Required: true, Description: "Name of the interface, static MAC address can be mapped to more that one port, including switch CPU port.", Elem: &schema.Schema{ - Type: schema.TypeString, + Type: schema.TypeString, DiffSuppressFunc: AlwaysPresentNotUserProvided, }, }, "redirect_to_cpu": { Type: schema.TypeBool, Optional: true, - Description: "Whether to redirect a frame to switch CPU port from a frame with matching MAC destination address "+ - "(matching destination or source address for CRS3xx series switches).", + Description: "Whether to redirect a frame to switch CPU port from a frame with matching MAC destination address " + + "(matching destination or source address for CRS3xx series switches).", }, "share_vlan_learned": { Type: schema.TypeBool, Optional: true, - Description: "Whether the static host MAC address lookup is used with shared-VLAN-learning (SVL) or "+ - "independent-VLAN-learning (IVL). The SVL mode is used for those VLAN entries that do not support IVL or IVL is "+ - "disabled (independent-learning=no).", + Description: "Whether the static host MAC address lookup is used with shared-VLAN-learning (SVL) or " + + "independent-VLAN-learning (IVL). The SVL mode is used for those VLAN entries that do not support IVL or IVL is " + + "disabled (independent-learning=no).", }, "switch": { Type: schema.TypeString, @@ -75,9 +75,9 @@ func ResourceInterfaceEthernetSwitchHost() *schema.Resource { Description: "Name of the switch to which the MAC address is going to be assigned to.", }, "vlan_id": { - Type: schema.TypeString, - Optional: true, - Description: "VLAN ID for the statically added MAC address entry.", + Type: schema.TypeInt, + Optional: true, + Description: "VLAN ID for the statically added MAC address entry.", ValidateFunc: validation.IntBetween(0, 4094), DiffSuppressFunc: AlwaysPresentNotUserProvided, }, diff --git a/routeros/resource_interface_ethernet_switch_host_test.go b/routeros/resource_interface_ethernet_switch_host_test.go index e0d9a4b2..cf03e127 100644 --- a/routeros/resource_interface_ethernet_switch_host_test.go +++ b/routeros/resource_interface_ethernet_switch_host_test.go @@ -6,4 +6,4 @@ import ( func TestAccInterfaceEthernetSwitchHost_basic(t *testing.T) { t.Log("Test skipped, The test is skipped, the resource is only available on real hardware.") -} \ No newline at end of file +} diff --git a/routeros/resource_interface_ethernet_switch_test.go b/routeros/resource_interface_ethernet_switch_test.go index 0bdb67aa..c334c59a 100644 --- a/routeros/resource_interface_ethernet_switch_test.go +++ b/routeros/resource_interface_ethernet_switch_test.go @@ -6,4 +6,4 @@ import ( func TestAccInterfaceEthernetSwitchTest_basic(t *testing.T) { t.Log("Test skipped, The test is skipped, the resource is only available on real hardware.") -} \ No newline at end of file +} diff --git a/routeros/resource_ip_dhcp_server_config.go b/routeros/resource_ip_dhcp_server_config.go index e010cbc5..6595dd05 100644 --- a/routeros/resource_ip_dhcp_server_config.go +++ b/routeros/resource_ip_dhcp_server_config.go @@ -18,24 +18,24 @@ func ResourceDhcpServerConfig() *schema.Resource { Description: "An option that enables accounting for DHCP leases.", }, "interim_update": { - Type: schema.TypeString, - Optional: true, - Default: "0s", - Description: "An option determining whether the DHCP server sends periodic updates to the accounting server during a lease.", + Type: schema.TypeString, + Optional: true, + Default: "0s", + Description: "An option determining whether the DHCP server sends periodic updates to the accounting server during a lease.", DiffSuppressFunc: TimeEquall, }, "radius_password": { - Type: schema.TypeString, - Optional: true, - Default: "empty", - Description: "An option to set the password parameter for the RADIUS server. This option is available in RouterOS starting from version 7.0.", + Type: schema.TypeString, + Optional: true, + Default: "empty", + Description: "An option to set the password parameter for the RADIUS server. This option is available in RouterOS starting from version 7.0.", ValidateFunc: validation.StringInSlice([]string{"empty", "same-as-user"}, false), }, "store_leases_disk": { - Type: schema.TypeString, - Optional: true, - Default: "5m", - Description: "An option of how often the DHCP leases will be stored on disk.", + Type: schema.TypeString, + Optional: true, + Default: "5m", + Description: "An option of how often the DHCP leases will be stored on disk.", DiffSuppressFunc: TimeEquall, }, } diff --git a/routeros/resource_ovpn_server.go b/routeros/resource_ovpn_server.go index 97b6003c..55a0ce8e 100644 --- a/routeros/resource_ovpn_server.go +++ b/routeros/resource_ovpn_server.go @@ -55,7 +55,7 @@ func ResourceOpenVPNServer() *schema.Resource { ValidateDiagFunc: ValidationMultiValInSlice([]string{ "null", "aes128-cbc", "aes128-gcm", "aes192-cbc", "aes192-gcm", "aes256-cbc", "aes256-gcm", "blowfish128", // Backward compatibility with ROS v7.7 - "aes128", "aes192", "aes256", + "aes128", "aes192", "aes256", }, false, false), }, "default_profile": { diff --git a/routeros/resource_radius.go b/routeros/resource_radius.go index 989e8d50..c1aea317 100644 --- a/routeros/resource_radius.go +++ b/routeros/resource_radius.go @@ -18,23 +18,23 @@ func ResourceRadius() *schema.Resource { Description: "An option whether the configuration is for the backup RADIUS server.", }, "accounting_port": { - Type: schema.TypeInt, - Optional: true, - Default: 1813, - Description: "RADIUS server port used for accounting.", + Type: schema.TypeInt, + Optional: true, + Default: 1813, + Description: "RADIUS server port used for accounting.", ValidateFunc: validation.IntBetween(0, 65535), }, "address": { Type: schema.TypeString, Required: true, Description: "IPv4 or IPv6 address of RADIUS server.", - ValidateFunc: validation.IsIPAddress, + ValidateFunc: validation.IsIPAddress, }, "authentication_port": { - Type: schema.TypeInt, - Optional: true, - Default: 1812, - Description: "RADIUS server port used for authentication.", + Type: schema.TypeInt, + Optional: true, + Default: 1812, + Description: "RADIUS server port used for authentication.", ValidateFunc: validation.IntBetween(0, 65535), }, "called_id": { @@ -56,10 +56,10 @@ func ResourceRadius() *schema.Resource { Description: "Microsoft Windows domain of client passed to RADIUS servers that require domain validation.", }, "protocol": { - Type: schema.TypeString, - Optional: true, - Default: "udp", - Description: "An option specifies the protocol to use when communicating with the RADIUS Server.", + Type: schema.TypeString, + Optional: true, + Default: "udp", + Description: "An option specifies the protocol to use when communicating with the RADIUS Server.", ValidateFunc: validation.StringInSlice([]string{"radsec", "udp"}, false), }, "realm": { @@ -85,17 +85,17 @@ func ResourceRadius() *schema.Resource { ValidateFunc: validation.IsIPAddress, }, "timeout": { - Type: schema.TypeString, - Optional: true, - Default: "300ms", - Description: "A timeout, after which the request should be resent.", + Type: schema.TypeString, + Optional: true, + Default: "300ms", + Description: "A timeout, after which the request should be resent.", DiffSuppressFunc: TimeEquall, }, } return &schema.Resource{ CreateContext: DefaultCreate(resSchema), - ReadContext: DefaultRead(resSchema), + ReadContext: DefaultRead(resSchema), UpdateContext: DefaultUpdate(resSchema), DeleteContext: DefaultDelete(resSchema), @@ -120,10 +120,10 @@ func ResourceRadiusIncoming() *schema.Resource { Description: "An option whether to accept the unsolicited messages.", }, "port": { - Type: schema.TypeInt, - Optional: true, - Default: 3799, - Description: "The port number to listen for the requests on.", + Type: schema.TypeInt, + Optional: true, + Default: 3799, + Description: "The port number to listen for the requests on.", ValidateFunc: validation.IntBetween(0, 65535), }, "vrf": { diff --git a/routeros/resource_system_logging.go b/routeros/resource_system_logging.go index f19d35d6..7a6bf652 100644 --- a/routeros/resource_system_logging.go +++ b/routeros/resource_system_logging.go @@ -39,8 +39,8 @@ func ResourceSystemLogging() *schema.Resource { ValidateFunc: validation.StringInSlice([]string{"disk", "echo", "memory", "remote"}, false), }, "default": { - Type: schema.TypeString, - Computed: true, + Type: schema.TypeString, + Computed: true, }, "prefix": { Type: schema.TypeString, From 23e93b92227ebfcd2d40e98af3854b10ec0133f8 Mon Sep 17 00:00:00 2001 From: Felix Date: Mon, 15 Jan 2024 22:36:50 +0300 Subject: [PATCH 6/7] feat(switch): Add support for interface/ethernet/switch/rule Fixes #325 --- routeros/provider.go | 46 +++-- ...resource_interface_ethernet_switch_rule.go | 191 ++++++++++++++++++ ...rce_interface_ethernet_switch_rule_test.go | 9 + 3 files changed, 231 insertions(+), 15 deletions(-) create mode 100644 routeros/resource_interface_ethernet_switch_rule.go create mode 100644 routeros/resource_interface_ethernet_switch_rule_test.go diff --git a/routeros/provider.go b/routeros/provider.go index f9d81d9d..4e07c384 100644 --- a/routeros/provider.go +++ b/routeros/provider.go @@ -15,9 +15,12 @@ func Provider() *schema.Provider { return &schema.Provider{ Schema: map[string]*schema.Schema{ "hosturl": { - Type: schema.TypeString, - Required: true, - DefaultFunc: schema.MultiEnvDefaultFunc([]string{"ROS_HOSTURL", "MIKROTIK_HOST"}, nil), + Type: schema.TypeString, + Required: true, + DefaultFunc: schema.MultiEnvDefaultFunc( + []string{"ROS_HOSTURL", "MIKROTIK_HOST"}, + nil, + ), Description: `URL of the MikroTik router, default is TLS connection to REST. * API: api[s]://host[:port] * api://router.local @@ -32,9 +35,12 @@ func Provider() *schema.Provider { `, }, "username": { - Type: schema.TypeString, - Required: true, - DefaultFunc: schema.MultiEnvDefaultFunc([]string{"ROS_USERNAME", "MIKROTIK_USER"}, nil), + Type: schema.TypeString, + Required: true, + DefaultFunc: schema.MultiEnvDefaultFunc( + []string{"ROS_USERNAME", "MIKROTIK_USER"}, + nil, + ), Description: `Username for the MikroTik WEB/Winbox. @@ -42,22 +48,31 @@ func Provider() *schema.Provider { `, }, "password": { - Type: schema.TypeString, - Optional: true, - DefaultFunc: schema.MultiEnvDefaultFunc([]string{"ROS_PASSWORD", "MIKROTIK_PASSWORD"}, nil), + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.MultiEnvDefaultFunc( + []string{"ROS_PASSWORD", "MIKROTIK_PASSWORD"}, + nil, + ), Description: "Password for the MikroTik user.", Sensitive: true, }, "ca_certificate": { - Type: schema.TypeString, - Optional: true, - DefaultFunc: schema.MultiEnvDefaultFunc([]string{"ROS_CA_CERTIFICATE", "MIKROTIK_CA_CERTIFICATE"}, nil), + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.MultiEnvDefaultFunc( + []string{"ROS_CA_CERTIFICATE", "MIKROTIK_CA_CERTIFICATE"}, + nil, + ), Description: "Path to MikroTik's certificate authority file.", }, "insecure": { - Type: schema.TypeBool, - Optional: true, - DefaultFunc: schema.MultiEnvDefaultFunc([]string{"ROS_INSECURE", "MIKROTIK_INSECURE"}, false), + Type: schema.TypeBool, + Optional: true, + DefaultFunc: schema.MultiEnvDefaultFunc( + []string{"ROS_INSECURE", "MIKROTIK_INSECURE"}, + false, + ), Description: "Whether to verify the SSL certificate or not.", }, }, @@ -112,6 +127,7 @@ func Provider() *schema.Provider { "routeros_interface_ethernet_switch_port": ResourceInterfaceEthernetSwitchPort(), "routeros_interface_ethernet_switch_port_isolation": ResourceInterfaceEthernetSwitchPortIsolation(), "routeros_interface_ethernet_switch_vlan": ResourceInterfaceEthernetSwitchVlan(), + "routeros_interface_ethernet_switch_rule": ResourceInterfaceEthernetSwitchRule(), "routeros_interface_gre": ResourceInterfaceGre(), "routeros_interface_vlan": ResourceInterfaceVlan(), "routeros_interface_vrrp": ResourceInterfaceVrrp(), diff --git a/routeros/resource_interface_ethernet_switch_rule.go b/routeros/resource_interface_ethernet_switch_rule.go new file mode 100644 index 00000000..e8817ac1 --- /dev/null +++ b/routeros/resource_interface_ethernet_switch_rule.go @@ -0,0 +1,191 @@ +package routeros + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +/* +??? +*/ + +// https://help.mikrotik.com/docs/display/ROS/Switch+Chip+Features#SwitchChipFeatures-RuleTable +func ResourceInterfaceEthernetSwitchRule() *schema.Resource { + resSchema := map[string]*schema.Schema{ + MetaResourcePath: PropResourcePath("/interface/ethernet/switch/rule"), + MetaId: PropId(Id), + + KeyComment: PropCommentRw, + KeyDisabled: PropDisabledRw, + KeyInvalid: PropInvalidRo, + + "copy_to_cpu": { + Type: schema.TypeBool, + Optional: true, + Description: "Whether to send a frame copy to switch CPU port from a frame with matching MAC destination address " + + "(matching destination or source address for CRS3xx series switches).", + }, + "dst_address": { + Type: schema.TypeString, + Optional: true, + Description: "Matching destination IP address and mask.", + }, + "dst_address6": { + Type: schema.TypeString, + Optional: true, + Description: "Matching destination IPv6 address and mask.", + }, + "dst_mac_address": { + Type: schema.TypeString, + Optional: true, + Description: "Matching destination MAC address and mask.", + }, + "dst_port": { + Type: schema.TypeInt, + Optional: true, + Description: "Matching destination protocol port number or range.", + ValidateFunc: validation.IntBetween(0, 65535), + }, + "dscp": { + Type: schema.TypeInt, + Optional: true, + Description: "Matching DSCP field of the packet.", + ValidateFunc: validation.IntBetween(0, 63), + }, + "flow_label": { + Type: schema.TypeInt, + Optional: true, + Description: "Matching IPv6 flow label.", + ValidateFunc: validation.IntBetween(0, 1048575), + }, + "mac_protocol": { + Type: schema.TypeString, + Optional: true, + Description: "Matching particular MAC protocol specified by protocol name or number (skips VLAN tags if any).", + }, + "mirror": { + Type: schema.TypeBool, + Optional: true, + Description: "Whether to send a frame copy to mirror-target port from a frame with matching MAC destination address " + + "(matching destination or source address for CRS3xx series switches).", + }, + "new_dst_ports": { + Type: schema.TypeString, + Optional: true, + Description: "Changes the destination port as specified, multiple ports allowed, including a switch CPU port. An empty " + + "setting will drop the packet. When the parameter is not used, the packet will be accepted.", + Elem: &schema.Schema{ + Type: schema.TypeString, + DiffSuppressFunc: AlwaysPresentNotUserProvided, + }, + }, + "new_vlan_id": { + Type: schema.TypeInt, + Optional: true, + Description: "Changes the VLAN ID to the specified value or adds a new VLAN tag if one was not already present " + + "(the property only applies to the Atheros8316, and 88E6393X switch chips).", + ValidateFunc: validation.IntBetween(0, 4094), + DiffSuppressFunc: AlwaysPresentNotUserProvided, + }, + "new_vlan_priority": { + Type: schema.TypeInt, + Optional: true, + Description: "Changes the VLAN priority field (priority code point, the property only applies to Atheros8327, QCA8337 " + + "and Atheros8316 switch chips).", + ValidateFunc: validation.IntBetween(0, 7), + DiffSuppressFunc: AlwaysPresentNotUserProvided, + }, + "ports": { + Type: schema.TypeString, + Required: true, + Description: "Name of the interface on which the rule will apply on the received traffic, multiple ports are allowed.", + Elem: &schema.Schema{ + Type: schema.TypeString, + DiffSuppressFunc: AlwaysPresentNotUserProvided, + }, + }, + "protocol": { + Type: schema.TypeString, + Optional: true, + Description: "Matching particular IP protocol specified by protocol name or number.", + }, + "rate": { + Type: schema.TypeInt, + Optional: true, + Description: "Sets ingress traffic limitation (bits per second) for matched traffic, can only be applied to the first 32 rule slots " + + "(the property only applies to Atheros8327/QCA8337 switch chips).", + ValidateFunc: validation.IntAtLeast(0), + DiffSuppressFunc: AlwaysPresentNotUserProvided, + }, + "redirect_to_cpu": { + Type: schema.TypeBool, + Optional: true, + Description: "Changes the destination port of a matching packet to the switch CPU.", + }, + "src_address": { + Type: schema.TypeString, + Optional: true, + Description: "Matching source IP address and mask.", + }, + "src_address6": { + Type: schema.TypeString, + Optional: true, + Description: "Matching source IPv6 address and mask.", + }, + "src_mac_address": { + Type: schema.TypeString, + Optional: true, + Description: "Matching source MAC address and mask.", + }, + "src_port": { + Type: schema.TypeInt, + Optional: true, + Description: "Matching source protocol port number or range.", + ValidateFunc: validation.IntBetween(0, 65535), + }, + "switch": { + Type: schema.TypeString, + Required: true, + Description: "Matching switch group on which will the rule apply.", + }, + "traffic_class": { + Type: schema.TypeInt, + Optional: true, + Description: "Matching IPv6 traffic class.", + ValidateFunc: validation.IntBetween(0, 255), + }, + "vlan_header": { + Type: schema.TypeString, + Optional: true, + Description: "Matching VLAN header, whether the VLAN header is present or not (the property only applies to the " + + "Atheros8316, Atheros8327, QCA8337, 88E6393X switch chips).", + ValidateFunc: validation.StringInSlice([]string{"not-present", "present"}, false), + }, + "vlan_id": { + Type: schema.TypeInt, + Optional: true, + Description: "Matching VLAN ID (the property only applies to the Atheros8316, Atheros8327, QCA8337, 88E6393X switch chips).", + ValidateFunc: validation.IntBetween(0, 4095), + DiffSuppressFunc: AlwaysPresentNotUserProvided, + }, + "vlan_priority": { + Type: schema.TypeInt, + Optional: true, + Description: "Matching VLAN priority (priority code point).", + ValidateFunc: validation.IntBetween(0, 7), + }, + } + + return &schema.Resource{ + CreateContext: DefaultCreate(resSchema), + ReadContext: DefaultRead(resSchema), + UpdateContext: DefaultUpdate(resSchema), + DeleteContext: DefaultSystemDelete(resSchema), + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: resSchema, + } +} diff --git a/routeros/resource_interface_ethernet_switch_rule_test.go b/routeros/resource_interface_ethernet_switch_rule_test.go new file mode 100644 index 00000000..76ffc424 --- /dev/null +++ b/routeros/resource_interface_ethernet_switch_rule_test.go @@ -0,0 +1,9 @@ +package routeros + +import ( + "testing" +) + +func TestAccInterfaceEthernetSwitchRule_basic(t *testing.T) { + t.Log("Test skipped, The test is skipped, the resource is only available on real hardware.") +} From 4de3c6bfbfdb0cffccf06d2f5447aa80b8b8d691 Mon Sep 17 00:00:00 2001 From: Felix Date: Mon, 15 Jan 2024 22:37:11 +0300 Subject: [PATCH 7/7] docs(switch): Add examples --- .../routeros_interface_ethernet_switch_host/import.sh | 3 +++ .../routeros_interface_ethernet_switch_host/resource.tf | 6 ++++++ .../routeros_interface_ethernet_switch_port/import.sh | 3 +++ .../routeros_interface_ethernet_switch_port/resource.tf | 4 ++++ .../import.sh | 3 +++ .../resource.tf | 4 ++++ .../routeros_interface_ethernet_switch_rule/import.sh | 3 +++ .../routeros_interface_ethernet_switch_rule/resource.tf | 5 +++++ .../routeros_interface_ethernet_switch_vlan/import.sh | 3 +++ .../routeros_interface_ethernet_switch_vlan/resource.tf | 6 ++++++ 10 files changed, 40 insertions(+) create mode 100644 examples/resources/routeros_interface_ethernet_switch_host/import.sh create mode 100644 examples/resources/routeros_interface_ethernet_switch_host/resource.tf create mode 100644 examples/resources/routeros_interface_ethernet_switch_port/import.sh create mode 100644 examples/resources/routeros_interface_ethernet_switch_port/resource.tf create mode 100644 examples/resources/routeros_interface_ethernet_switch_port_isolation/import.sh create mode 100644 examples/resources/routeros_interface_ethernet_switch_port_isolation/resource.tf create mode 100644 examples/resources/routeros_interface_ethernet_switch_rule/import.sh create mode 100644 examples/resources/routeros_interface_ethernet_switch_rule/resource.tf create mode 100644 examples/resources/routeros_interface_ethernet_switch_vlan/import.sh create mode 100644 examples/resources/routeros_interface_ethernet_switch_vlan/resource.tf diff --git a/examples/resources/routeros_interface_ethernet_switch_host/import.sh b/examples/resources/routeros_interface_ethernet_switch_host/import.sh new file mode 100644 index 00000000..77906582 --- /dev/null +++ b/examples/resources/routeros_interface_ethernet_switch_host/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/switch/host get [print show-ids]] +terraform import routeros_interface_ethernet_switch_host.test *0 diff --git a/examples/resources/routeros_interface_ethernet_switch_host/resource.tf b/examples/resources/routeros_interface_ethernet_switch_host/resource.tf new file mode 100644 index 00000000..58a5bc4e --- /dev/null +++ b/examples/resources/routeros_interface_ethernet_switch_host/resource.tf @@ -0,0 +1,6 @@ +resource "routeros_interface_ethernet_switch_host" "test" { + switch = "switch1" + mac_address = "00:00:00:00:00:00" + ports = ["ether1"] + mirror = true +} \ No newline at end of file diff --git a/examples/resources/routeros_interface_ethernet_switch_port/import.sh b/examples/resources/routeros_interface_ethernet_switch_port/import.sh new file mode 100644 index 00000000..4304b0eb --- /dev/null +++ b/examples/resources/routeros_interface_ethernet_switch_port/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/switch/port get [print show-ids]] +terraform import routeros_interface_ethernet_switch_port.test *1 diff --git a/examples/resources/routeros_interface_ethernet_switch_port/resource.tf b/examples/resources/routeros_interface_ethernet_switch_port/resource.tf new file mode 100644 index 00000000..119a2a83 --- /dev/null +++ b/examples/resources/routeros_interface_ethernet_switch_port/resource.tf @@ -0,0 +1,4 @@ +resource "routeros_interface_ethernet_switch_port" "test" { + name = "ether1" + vlan_mode = "check" +} diff --git a/examples/resources/routeros_interface_ethernet_switch_port_isolation/import.sh b/examples/resources/routeros_interface_ethernet_switch_port_isolation/import.sh new file mode 100644 index 00000000..4e87f498 --- /dev/null +++ b/examples/resources/routeros_interface_ethernet_switch_port_isolation/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/switch/port-isolation get [print show-ids]] +terraform import routeros_interface_ethernet_switch_port_isolation.test *1 diff --git a/examples/resources/routeros_interface_ethernet_switch_port_isolation/resource.tf b/examples/resources/routeros_interface_ethernet_switch_port_isolation/resource.tf new file mode 100644 index 00000000..6746f9df --- /dev/null +++ b/examples/resources/routeros_interface_ethernet_switch_port_isolation/resource.tf @@ -0,0 +1,4 @@ +resource "routeros_interface_ethernet_switch_port_isolation" "test" { + name = "ether1" + forwarding_override = "ether1" +} diff --git a/examples/resources/routeros_interface_ethernet_switch_rule/import.sh b/examples/resources/routeros_interface_ethernet_switch_rule/import.sh new file mode 100644 index 00000000..f6aaf17b --- /dev/null +++ b/examples/resources/routeros_interface_ethernet_switch_rule/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/switch/rule get [print show-ids]] +terraform import routeros_interface_ethernet_switch_rule.test *0 diff --git a/examples/resources/routeros_interface_ethernet_switch_rule/resource.tf b/examples/resources/routeros_interface_ethernet_switch_rule/resource.tf new file mode 100644 index 00000000..4fabb339 --- /dev/null +++ b/examples/resources/routeros_interface_ethernet_switch_rule/resource.tf @@ -0,0 +1,5 @@ +resource "routeros_interface_ethernet_switch_rule" "test" { + switch = "switch1" + ports = ["ether1"] + copy_to_cpu = true +} diff --git a/examples/resources/routeros_interface_ethernet_switch_vlan/import.sh b/examples/resources/routeros_interface_ethernet_switch_vlan/import.sh new file mode 100644 index 00000000..b481f7d3 --- /dev/null +++ b/examples/resources/routeros_interface_ethernet_switch_vlan/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/switch/vlan get [print show-ids]] +terraform import routeros_interface_ethernet_switch_vlan.test *0 diff --git a/examples/resources/routeros_interface_ethernet_switch_vlan/resource.tf b/examples/resources/routeros_interface_ethernet_switch_vlan/resource.tf new file mode 100644 index 00000000..b9f20861 --- /dev/null +++ b/examples/resources/routeros_interface_ethernet_switch_vlan/resource.tf @@ -0,0 +1,6 @@ +resource "routeros_interface_ethernet_switch_vlan" "test" { + switch = "switch1" + ports = ["ether1"] + vlan_id = 10 + independent_learning = true +} \ No newline at end of file