diff --git a/routeros/capsman/resource_capsman_channel.go b/routeros/capsman/resource_capsman_channel.go deleted file mode 100644 index e122449a..00000000 --- a/routeros/capsman/resource_capsman_channel.go +++ /dev/null @@ -1,87 +0,0 @@ -package routeros - -import ( - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -func ResourceCapsManChannel() *schema.Resource { - // return &schema.Resource{ - // Create: resourceCapsManChannelCreate, - // Read: resourceCapsManChannelRead, - // Update: resourceCapsManChannelUpdate, - // Delete: resourceCapsManChannelDelete, - // Importer: &schema.ResourceImporter{ - // StateContext: schema.ImportStatePassthroughContext, - // }, - - resSchema := map[string]*schema.Schema{ - MetaResourcePath: PropResourcePath("/caps-man/channel"), - MetaId: PropId(Name), - - "save_selected": { - Type: schema.TypeBool, - Optional: true, - Computed: true, - }, - "width": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "control_channel_width": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - KeyName: PropNameRw, - KeyComment: PropCommentRw, - "band": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "reselect_interval": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "extension_channel": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "frequency": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - }, - "secondary_frequency": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "tx_power": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - }, - "skip_dfs_channels": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - } - - return &schema.Resource{ - CreateContext: DefaultCreate(resSchema), - ReadContext: DefaultRead(resSchema), - UpdateContext: DefaultUpdate(resSchema), - DeleteContext: DefaultDelete(resSchema), - - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - - Schema: resSchema, - } -} diff --git a/routeros/capsman/resource_capsman_configuration.go b/routeros/capsman/resource_capsman_configuration.go deleted file mode 100644 index 837a40bf..00000000 --- a/routeros/capsman/resource_capsman_configuration.go +++ /dev/null @@ -1,308 +0,0 @@ -package routeros - -import ( - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -func ResourceCapsManConfiguration() *schema.Resource { - resSchema := map[string]*schema.Schema{ - MetaResourcePath: PropResourcePath("/caps-man/configuration"), - MetaId: PropId(Name), - - KeyName: PropNameRw, - KeyComment: PropCommentRw, - "channel": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "channel_save_selected": { - Type: schema.TypeBool, - Optional: true, - //Default: false, - }, - "channel_width": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "channel_control_channel_width": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "channel_band": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "channel_reselect_interval": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "channel_extension_channel": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "channel_frequency": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - }, - "channel_secondary_frequency": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "channel_tx_power": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - }, - "channel_skip_dfs_channels": { - Type: schema.TypeBool, - Optional: true, - //Default: false, - }, - "country": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "datapath_bridge": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "datapath_bridge_cost": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "datapath_bridge_horizon": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "datapath_interface_list": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "datapath_l2mtu": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "datapath_local_forwarding": { - Type: schema.TypeString, - Optional: true, - //Default: "false", - }, - "datapath_client_to_client_forwarding": { - Type: schema.TypeString, - Optional: true, - //Default: "false", - }, - "datapath_mtu": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "datapath_openflow_switch": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "datapath_vlan_mode": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "datapath_vlan_id": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - }, - "disconnect_timeout": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "distance": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "frame_lifetime": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "guard_interval": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "hide_ssid": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - "hw_protection_mode": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "hw_retries": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "installation": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "keepalive_frames": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "load_balancing_group": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "max_sta_count": { - Type: schema.TypeInt, - Optional: true, - Default: 2007, - }, - "mode": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "multicast_helper": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "rates": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "rates_basic": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "rates_supported": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "rates_ht_basic_mcs": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "rates_ht_supported_mcs": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "rates_vht_basic_mcs": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "rates_vht_supported_mcs": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "rx_chains": { - Type: schema.TypeString, - Optional: true, - Default: "0", - }, - "security": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "security_group_encryption": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "security_authentication_types": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "security_eap_methods": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "security_eap_radius_accounting": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "security_encryption": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "security_passphrase": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "security_group_key_update": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "security_tls_certificate": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "security_tls_mode": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "ssid": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "tx_chains": { - Type: schema.TypeString, - Optional: true, - Default: "0", - }, - } - - return &schema.Resource{ - CreateContext: DefaultCreate(resSchema), - ReadContext: DefaultRead(resSchema), - UpdateContext: DefaultUpdate(resSchema), - DeleteContext: DefaultDelete(resSchema), - - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - - Schema: resSchema, - } -} diff --git a/routeros/capsman/resource_capsman_configuration_test.go b/routeros/capsman/resource_capsman_configuration_test.go deleted file mode 100644 index 1d97ee96..00000000 --- a/routeros/capsman/resource_capsman_configuration_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package routeros - -import ( - "fmt" - "testing" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" -) - -const testCapsManConfigurationAddress = "routeros_capsman_configuration.test_configuration" - -func TestAccCapsManConfigurationTest_basic(t *testing.T) { - for _, name := range testNames { - t.Run(name, func(t *testing.T) { - resource.Test(t, resource.TestCase{ - PreCheck: func() { - testAccPreCheck(t) - testSetTransportEnv(t, name) - }, - ProviderFactories: testAccProviderFactories, - CheckDestroy: testCheckResourceDestroy("/caps-man/configuration", "routeros_capsman_configuration"), - Steps: []resource.TestStep{ - { - Config: testAccCapsManConfigurationConfig(), - Check: resource.ComposeTestCheckFunc( - testAccCheckCapsManConfigurationExists(testCapsManConfigurationAddress), - resource.TestCheckResourceAttr(testCapsManConfigurationAddress, "name", "test_configuration"), - ), - }, - }, - }) - }) - } -} - -func testAccCheckCapsManConfigurationExists(name string) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[name] - if !ok { - return fmt.Errorf("not found: %s", name) - } - - if rs.Primary.ID == "" { - return fmt.Errorf("no id is set") - } - - return nil - } -} - -func testAccCapsManConfigurationConfig() string { - return ` - -provider "routeros" { - insecure = true -} - -resource "routeros_capsman_configuration" "test_configuration" { - name = "test_configuration" - } - -` -} diff --git a/routeros/capsman/resource_capsman_datapath.go b/routeros/capsman/resource_capsman_datapath.go deleted file mode 100644 index a40d0dbd..00000000 --- a/routeros/capsman/resource_capsman_datapath.go +++ /dev/null @@ -1,79 +0,0 @@ -package routeros - -import ( - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -func ResourceCapsManDatapath() *schema.Resource { - resSchema := map[string]*schema.Schema{ - MetaResourcePath: PropResourcePath("/caps-man/datapath"), - MetaId: PropId(Name), - - "bridge": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "bridge_cost": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - KeyName: PropNameRw, - KeyComment: PropCommentRw, - "bridge_horizon": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "interface_list": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - KeyL2Mtu: PropL2MtuRo, - "local_forwarding": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "client_to_client_forwarding": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "mtu": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "openflow_switch": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "vlan_mode": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "vlan_id": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - }, - } - - return &schema.Resource{ - CreateContext: DefaultCreate(resSchema), - ReadContext: DefaultRead(resSchema), - UpdateContext: DefaultUpdate(resSchema), - DeleteContext: DefaultDelete(resSchema), - - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - - Schema: resSchema, - } -} diff --git a/routeros/capsman/resource_capsman_manager.go b/routeros/capsman/resource_capsman_manager.go deleted file mode 100644 index 129e7c4e..00000000 --- a/routeros/capsman/resource_capsman_manager.go +++ /dev/null @@ -1,56 +0,0 @@ -package routeros - -import ( - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -func ResourceCapsManManager() *schema.Resource { - - resSchema := map[string]*schema.Schema{ - MetaResourcePath: PropResourcePath("/caps-man/manager"), - MetaId: PropId(Name), - "enabled": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - "upgrade_policy": { - Type: schema.TypeString, - Optional: true, - Default: "none", - }, - "certificate": { - Type: schema.TypeString, - Optional: true, - Default: "none", - }, - "ca_certificate": { - Type: schema.TypeString, - Optional: true, - Default: "none", - }, - "require_peer_certificate": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - "package_path": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - } - - return &schema.Resource{ - CreateContext: DefaultCreate(resSchema), - ReadContext: DefaultRead(resSchema), - UpdateContext: DefaultUpdate(resSchema), - DeleteContext: DefaultDelete(resSchema), - - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - - Schema: resSchema, - } -} diff --git a/routeros/capsman/resource_capsman_manager_test.go b/routeros/capsman/resource_capsman_manager_test.go deleted file mode 100644 index e2faea8c..00000000 --- a/routeros/capsman/resource_capsman_manager_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package routeros - -import ( - "fmt" - "testing" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" -) - -const testCapsManManagerAddress = "routeros_capsman_manager.test_manager" - -func TestAccCapsManManagerTest_basic(t *testing.T) { - for _, name := range testNames { - t.Run(name, func(t *testing.T) { - resource.Test(t, resource.TestCase{ - PreCheck: func() { - testAccPreCheck(t) - testSetTransportEnv(t, name) - }, - ProviderFactories: testAccProviderFactories, - CheckDestroy: testCheckResourceDestroy("/caps-man/manager", "routeros_capsman_manager"), - Steps: []resource.TestStep{ - { - Config: testAccCapsManManagerConfig(), - Check: resource.ComposeTestCheckFunc( - testAccCheckCapsManManagerExists(testCapsManManagerAddress), - resource.TestCheckResourceAttr(testCapsManManagerAddress, "name", "test_manager"), - ), - }, - }, - }) - }) - } -} - -func testAccCheckCapsManManagerExists(address string) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[address] - if !ok { - return fmt.Errorf("not found: %s", address) - } - - if rs.Primary.ID == "" { - return fmt.Errorf("no id is set") - } - - return nil - } -} - -func testAccCapsManManagerConfig() string { - return ` - -provider "routeros" { - insecure = true -} - -resource "routeros_capsman_manager" "test_manager" { - enabled = "true" - } - -` -} diff --git a/routeros/capsman/resource_capsman_provisioning.go b/routeros/capsman/resource_capsman_provisioning.go deleted file mode 100644 index 097e79c6..00000000 --- a/routeros/capsman/resource_capsman_provisioning.go +++ /dev/null @@ -1,80 +0,0 @@ -package routeros - -import ( - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -func ResourceCapsManProvisioning() *schema.Resource { - resSchema := map[string]*schema.Schema{ - MetaResourcePath: PropResourcePath("/caps-man/provisioning"), - MetaId: PropId(Name), - - "action": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "common_name_regexp": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "name_prefix": { - Type: schema.TypeString, - Optional: true, - }, - "comment": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "hw_supported_modes": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "ip_address_ranges": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "identity_regexp": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "master_configuration": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "name_format": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "radio_mac": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "slave_configurations": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - } - - return &schema.Resource{ - CreateContext: DefaultCreate(resSchema), - ReadContext: DefaultRead(resSchema), - UpdateContext: DefaultUpdate(resSchema), - DeleteContext: DefaultDelete(resSchema), - - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - - Schema: resSchema, - } -} diff --git a/routeros/capsman/resource_capsman_security.go b/routeros/capsman/resource_capsman_security.go deleted file mode 100644 index a0558e6c..00000000 --- a/routeros/capsman/resource_capsman_security.go +++ /dev/null @@ -1,77 +0,0 @@ -package routeros - -import ( - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -func ResourceCapsManSecurity() *schema.Resource { - - resSchema := map[string]*schema.Schema{ - MetaResourcePath: PropResourcePath("/caps-man/security"), - MetaId: PropId(Name), - "group_encryption": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "authentication_types": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "eap_methods": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "eap_radius_accounting": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - KeyName: PropNameRw, - "comment": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "encryption": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "passphrase": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "group_key_update": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "tls_certificate": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "tls_mode": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - } - - return &schema.Resource{ - CreateContext: DefaultCreate(resSchema), - ReadContext: DefaultRead(resSchema), - UpdateContext: DefaultUpdate(resSchema), - DeleteContext: DefaultDelete(resSchema), - - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - - Schema: resSchema, - } -} diff --git a/routeros/provider.go b/routeros/provider.go index b4839417..d265cec9 100644 --- a/routeros/provider.go +++ b/routeros/provider.go @@ -126,13 +126,18 @@ func Provider() *schema.Provider { // TODO: Review whether capsman resources need updating given wifiwave2. // wifiwave2 is getting support for capsman in 7.8. // Should we support both legacy capsman _and_ wifiwave2 capsman? - - // "routeros_capsman_channel": ResourceCapsManChannel(), - // "routeros_capsman_configuration": ResourceCapsManConfiguration(), - // "routeros_capsman_datapath": ResourceCapsManDatapath(), - // "routeros_capsman_manager": ResourceCapsManManager(), - // "routeros_capsman_provisioning": ResourceCapsManProvisioning(), - // "routeros_capsman_security": ResourceCapsManSecurity(), + // https://help.mikrotik.com/docs/display/ROS/WifiWave2#WifiWave2-WifiWave2CAPsMAN + + // CAPsMAN Objects + "routeros_capsman_channel": ResourceCapsManChannel(), + "routeros_capsman_configuration": ResourceCapsManConfiguration(), + "routeros_capsman_datapath": ResourceCapsManDatapath(), + "routeros_capsman_aaa": ResourceCapsManAaa(), + "routeros_capsman_manager": ResourceCapsManManager(), + "routeros_capsman_manager_interface": ResourceCapsManManagerInterface(), + "routeros_capsman_provisioning": ResourceCapsManProvisioning(), + "routeros_capsman_rates": ResourceCapsManRates(), + "routeros_capsman_security": ResourceCapsManSecurity(), }, DataSourcesMap: map[string]*schema.Resource{ "routeros_interfaces": DatasourceInterfaces(), diff --git a/routeros/resource_capsman_channel.go b/routeros/resource_capsman_channel.go new file mode 100644 index 00000000..163b3434 --- /dev/null +++ b/routeros/resource_capsman_channel.go @@ -0,0 +1,113 @@ +package routeros + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +/* + { + ".id": "*1", + "band": "2ghz-b", + "control-channel-width": "5mhz", + "extension-channel": "disabled", + "frequency": "2112", + "name": "channel1", + "reselect-interval": "10m", + "save-selected": "true", + "secondary-frequency": "disabled", + "skip-dfs-channels": "true", + "tx-power": "-20" + } +*/ + +// https://help.mikrotik.com/docs/display/ROS/CAPsMAN +func ResourceCapsManChannel() *schema.Resource { + + resSchema := map[string]*schema.Schema{ + MetaResourcePath: PropResourcePath("/caps-man/channel"), + MetaId: PropId(Name), + + "band": { + Type: schema.TypeString, + Optional: true, + Description: "Define operational radio frequency band and mode taken from hardware capability of wireless card.", + ValidateFunc: validation.StringInSlice([]string{"2ghz-b", "2ghz-b/g", "2ghz-b/g/n", "2ghz-onlyg", "2ghz-onlyn", + "5ghz-a", "5ghz-a/n", "5ghz-onlyn", "5ghz-a/n/ac", "5ghz-only-ac"}, false), + }, + KeyComment: PropCommentRw, + "control_channel_width": { + Type: schema.TypeString, + Optional: true, + Description: "Control channel width.", + ValidateFunc: validation.StringInSlice([]string{"5mhz", "10mhz", "20mhz", "40mhz-turbo"}, false), + }, + "extension_channel": { + Type: schema.TypeString, + Optional: true, + Description: "Extension channel configuration. (E.g. Ce = extension channel is above Control channel, " + + "eC = extension channel is below Control channel)", + ValidateFunc: validation.StringInSlice([]string{"Ce", "Ceee", "eC", "eCee", "eeCe", "eeeC", "xx", "xxxx", + "disabled"}, false), + }, + "frequency": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + Description: "Channel frequency value in MHz on which AP will operate. If left blank, CAPsMAN will " + + "automatically determine the best frequency that is least occupied.", + }, + KeyName: PropNameRw, + "reselect_interval": { + Type: schema.TypeString, + Optional: true, + Description: "The interval after which the least occupied frequency is chosen, can be defined as a random " + + "interval, ex. as '30m..60m'. Works only if channel.frequency is left blank.", + // We may need to write a custom DiffSuppressFunc. + // DiffSuppressFunc: TimeEquall, not for time ranges + }, + "save_selected": { + Type: schema.TypeBool, + Optional: true, + Description: "If channel frequency is chosen automatically and channel.reselect-interval is used, then " + + "saves the last picked frequency.", + }, + "secondary_frequency": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies the second frequency that will be used for 80+80MHz configuration. " + + "Set it to Disabled in order to disable 80+80MHz capability.", + }, + "skip_dfs_channels": { + Type: schema.TypeBool, + Optional: true, + Description: "If channel.frequency is left blank, the selection will skip DFS channels.", + }, + "tx_power": { + Type: schema.TypeInt, + Optional: true, + Description: "TX Power for CAP interface (for the whole interface not for individual chains) in dBm. " + + "It is not possible to set higher than allowed by country regulations or interface. By " + + "default max allowed by country or interface is used.", + ValidateFunc: validation.IntBetween(-30, 40), + }, + "width": { + Type: schema.TypeString, + Optional: true, + Description: "Channel Width in MHz.", + }, + } + + return &schema.Resource{ + CreateContext: DefaultCreate(resSchema), + ReadContext: DefaultRead(resSchema), + UpdateContext: DefaultUpdate(resSchema), + DeleteContext: DefaultDelete(resSchema), + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: resSchema, + } +} diff --git a/routeros/resource_capsman_channel_test.go b/routeros/resource_capsman_channel_test.go index fd2c3e4f..f340ddce 100644 --- a/routeros/resource_capsman_channel_test.go +++ b/routeros/resource_capsman_channel_test.go @@ -50,15 +50,20 @@ func testAccCheckCapsManChannelExists(name string) resource.TestCheckFunc { } func testAccCapsManChannelConfig() string { - return ` - -provider "routeros" { - insecure = true -} + return providerConfig + ` resource "routeros_capsman_channel" "test_channel" { - name = "test_channel" - } - + name = "test_channel" + comment = "test_channel" + band = "2ghz-b/g/n" + control_channel_width = "10mhz" + extension_channel = "eCee" + frequency = 2412 + reselect_interval = "1h" + save_selected = true + secondary_frequency = "disabled" + skip_dfs_channels = true + tx_power = 20 +} ` } diff --git a/routeros/resource_capsman_configuration.go b/routeros/resource_capsman_configuration.go new file mode 100644 index 00000000..cbe33fae --- /dev/null +++ b/routeros/resource_capsman_configuration.go @@ -0,0 +1,216 @@ +package routeros + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +/* + { + ".id": "*1", + "channel": "channel-cfg", + "comment": "Comment", + "country": "no_country_set", + "datapath": "datapath-cfg", + "disconnect-timeout": "1s150ms", + "distance": "indoors", + "frame-lifetime": "120ms", + "guard-interval": "long", + "hide-ssid": "true", + "hw-protection-mode": "rts-cts", + "hw-retries": "1", + "installation": "indoor", + "keepalive-frames": "enabled", + "load-balancing-group": "", + "max-sta-count": "1", + "mode": "ap", + "multicast-helper": "full", + "name": "cfg1", + "rates": "rate-cfg", + "rx-chains": "1,3", + "security": "security-cfg", + "ssid": "SSID", + "tx-chains": "0,2" + } +*/ + +// https://help.mikrotik.com/docs/display/ROS/CAPsMAN +func ResourceCapsManConfiguration() *schema.Resource { + resSchema := map[string]*schema.Schema{ + MetaResourcePath: PropResourcePath("/caps-man/configuration"), + MetaId: PropId(Name), + MetaTransformSet: PropTransformSet(`"channel": "channel.config", "datapath": "datapath.config", + "rates": "rates.config", "security": "security.config"`), + + "channel": { + Type: schema.TypeMap, + Optional: true, + Description: "Channel inline settings.", + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + KeyComment: PropCommentRw, + "country": { + Type: schema.TypeString, + Optional: true, + Description: "Limits available bands, frequencies and maximum transmit power for each frequency. Also " + + "specifies default value of scan-list. Value no_country_set is an FCC compliant set of channels.", + }, + "datapath": { + Type: schema.TypeMap, + Optional: true, + Description: "Datapath inline settings.", + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "disconnect_timeout": { + Type: schema.TypeString, + Optional: true, + Description: "This interval is measured from third sending failure on the lowest data rate. At this point " + + "3 * (hw-retries + 1) frame transmits on the lowest data rate had failed. During disconnect-timeout packet " + + "transmission will be retried with on-fail-retry-time interval. If no frame can be transmitted successfully " + + `during disconnect-timeout, the connection is closed, and this event is logged as "extensive data loss". ` + + "Successful frame transmission resets this timer.", + DiffSuppressFunc: TimeEquall, + }, + "distance": { + Type: schema.TypeString, + Optional: true, + Description: "How long to wait for confirmation of unicast frames (ACKs) before considering transmission " + + "unsuccessful, or in short ACK-Timeout.", + }, + "frame_lifetime": { + Type: schema.TypeString, + Optional: true, + Description: "Discard frames that have been queued for sending longer than frame-lifetime. By default, when " + + "value of this property is 0, frames are discarded only after connection is closed (format: 0.00 sec).", + DiffSuppressFunc: TimeEquall, + }, + "guard_interval": { + Type: schema.TypeString, + Optional: true, + Description: "Whether to allow use of short guard interval (refer to 802.11n MCS specification to see how " + + `this may affect throughput). "any" will use either short or long, depending on data rate, "long" will ` + + "use long.", + ValidateFunc: validation.StringInSlice([]string{"any ", "long"}, false), + }, + "hide_ssid": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + Description: "This property has effect only in AP mode. Setting it to yes can remove this network from " + + "the list of wireless networks that are shown by some client software. Changing this setting does not " + + "improve the security of the wireless network, because SSID is included in other frames sent by the AP.", + }, + "hw_protection_mode": { + Type: schema.TypeString, + Optional: true, + Description: "Frame protection support property. " + + "[See docs](https://wiki.mikrotik.com/wiki/Manual:Interface/Wireless#Frame_protection_support_(RTS/CTS)).", + ValidateFunc: validation.StringInSlice([]string{"cts-to-self", "none", "rts-cts"}, false), + }, + "hw_retries": { + Type: schema.TypeInt, + Optional: true, + Description: "Number of times sending frame is retried without considering it a transmission failure. " + + "[See docs](https://wiki.mikrotik.com/wiki/Manual:Interface/Wireless)", + ValidateFunc: validation.IntBetween(0, 15), + }, + "installation": { + Type: schema.TypeString, + Optional: true, + Description: "Adjusts scan-list to use indoor, outdoor or all frequencies for the country that is set.", + ValidateFunc: validation.StringInSlice([]string{"any", "indoor", "outdoor"}, false), + }, + "keepalive_frames": { + Type: schema.TypeString, + Optional: true, + Description: `If a client has not communicated for around 20 seconds, AP sends a "keepalive-frame".`, + ValidateFunc: validation.StringInSlice([]string{"enabled", "disabled"}, false), + }, + "load_balancing_group": { + Type: schema.TypeString, + Optional: true, + Description: "Tags the interface to the load balancing group. For a client to connect to interface in this " + + "group, the interface should have the same number of already connected clients as all other interfaces " + + "in the group or smaller. Useful in setups where ranges of CAPs mostly overlap.", + }, + "max_sta_count": { + Type: schema.TypeInt, + Optional: true, + Description: "Maximum number of associated clients.", + ValidateFunc: validation.IntBetween(1, 2007), + }, + "mode": { + Type: schema.TypeString, + Optional: true, + Description: "Set operational mode. Only **ap** currently supported.", + ValidateFunc: validation.StringInSlice([]string{"ap"}, false), + }, + KeyName: PropNameRw, + "multicast_helper": { + Type: schema.TypeString, + Optional: true, + Description: "When set to full multicast packets will be sent with unicast destination MAC address, " + + "resolving multicast problem on a wireless link. This option should be enabled only on the access " + + "point, clients should be configured in station-bridge mode.", + ValidateFunc: validation.StringInSlice([]string{"default", "disabled", "full"}, false), + }, + "rates": { + Type: schema.TypeMap, + Optional: true, + Description: "Rates inline settings.", + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "rx_chains": { + Type: schema.TypeList, + Optional: true, + Description: "Which antennas to use for receive.", + Elem: &schema.Schema{ + Type: schema.TypeInt, + ValidateFunc: validation.IntBetween(0, 3), + }, + }, + "security": { + Type: schema.TypeMap, + Optional: true, + Description: "Security inline settings.", + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "ssid": { + Type: schema.TypeString, + Optional: true, + Description: "SSID (service set identifier) is a name broadcast in the beacons that identifies " + + "wireless network.", + ValidateFunc: validation.StringLenBetween(0, 32), + }, + "tx_chains": { + Type: schema.TypeList, + Optional: true, + Description: "Which antennas to use for transmit.", + Elem: &schema.Schema{ + Type: schema.TypeInt, + ValidateFunc: validation.IntBetween(0, 3), + }, + }, + } + + return &schema.Resource{ + CreateContext: DefaultCreate(resSchema), + ReadContext: DefaultRead(resSchema), + UpdateContext: DefaultUpdate(resSchema), + DeleteContext: DefaultDelete(resSchema), + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: resSchema, + } +} diff --git a/routeros/resource_capsman_configuration_test.go b/routeros/resource_capsman_configuration_test.go new file mode 100644 index 00000000..3f66312c --- /dev/null +++ b/routeros/resource_capsman_configuration_test.go @@ -0,0 +1,170 @@ +package routeros + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +const testCapsManConfigurationAddress = "routeros_capsman_configuration.test_configuration" + +func TestAccCapsManConfigurationTest_basic(t *testing.T) { + for _, name := range testNames { + t.Run(name, func(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testSetTransportEnv(t, name) + }, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testCheckResourceDestroy("/caps-man/configuration", "routeros_capsman_configuration"), + Steps: []resource.TestStep{ + { + Config: testAccCapsManConfigurationConfig(0), + Check: resource.ComposeTestCheckFunc( + testAccCheckCapsManConfigurationExists(testCapsManConfigurationAddress), + resource.TestCheckResourceAttr(testCapsManConfigurationAddress, "name", "test_configuration"), + ), + }, + { + Config: testAccCapsManConfigurationConfig(1), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("routeros_capsman_configuration.test_configuration_2", + "channel.config"), + resource.TestCheckResourceAttrSet("routeros_capsman_configuration.test_configuration_2", + "datapath.config"), + resource.TestCheckResourceAttrSet("routeros_capsman_configuration.test_configuration_2", + "rates.config"), + resource.TestCheckResourceAttrSet("routeros_capsman_configuration.test_configuration_2", + "security.config"), + ), + Destroy: false, + }, + }, + }) + }) + } +} + +func testAccCheckCapsManConfigurationExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("not found: %s", name) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("no id is set") + } + + return nil + } +} + +func testAccCapsManConfigurationConfig(n int) string { + tests := []string{` +resource "routeros_capsman_configuration" "test_configuration" { + comment = "Comment" + country = "no_country_set" + disconnect_timeout = "1s150ms" + distance = "indoors" + frame_lifetime = "0.12" // 120ms + guard_interval = "long" + hide_ssid = true + hw_protection_mode = "rts-cts" + hw_retries = 1 + installation = "indoor" + keepalive_frames = "enabled" + load_balancing_group = "" + max_sta_count = 1 + mode = "ap" + multicast_helper = "full" + name = "test_configuration" + rx_chains = [1, 3] + ssid = "SSID" + tx_chains = [0, 2] + }`, ` +resource "routeros_capsman_channel" "test_channel" { + name = "test-channel-config" +} + +resource "routeros_capsman_datapath" "test_datapath" { + name = "test-datapath-config" +} + +resource "routeros_capsman_rates" "test_rates" { + name = "test-rates-config" +} + +resource "routeros_capsman_security" "test_security" { + name = "test-security-config" +} + +resource "routeros_capsman_configuration" "test_configuration_2" { + name = "test_configuration" + + channel = { + config = "${routeros_capsman_channel.test_channel.name}" + band = "2ghz-b/g/n" + control_channel_width = "10mhz" + extension_channel = "eCee" + frequency = 2412 + reselect_interval = "1h" + save_selected = "true" + secondary_frequency = "disabled" + skip_dfs_channels = "true" + tx_power = 20 + } + + datapath = { + config = "${routeros_capsman_datapath.test_datapath.name}" + arp = "local-proxy-arp" + bridge = "bridge" + bridge_cost = "100" + bridge_horizon = "200" + client_to_client_forwarding = "true" + interface_list = "static" + l2mtu = "1450" + local_forwarding = "true" + mtu = "1500" + vlan_id = "101" + vlan_mode = "no-tag" + // openflow_switch = "aaa" + } + + rates = { + config = "${routeros_capsman_rates.test_rates.name}" + basic = "1Mbps,5.5Mbps,6Mbps,18Mbps,36Mbps,54Mbps" + ht_basic_mcs = "mcs-0,mcs-7,mcs-11,mcs-14,mcs-16,mcs-21" + ht_supported_mcs = "mcs-3,mcs-8,mcs-10,mcs-13,mcs-17,mcs-18" + supported = "2Mbps,11Mbps,9Mbps,12Mbps,24Mbps,48Mbps" + vht_basic_mcs = "none" + vht_supported_mcs = "mcs0-9,mcs0-7" + } + + security = { + config = "${routeros_capsman_security.test_security.name}" + authentication_types = "wpa-psk,wpa-eap" + disable_pmkid = "true" + eap_methods = "eap-tls,passthrough" + eap_radius_accounting = "true" + encryption = "aes-ccm,tkip" + group_encryption = "aes-ccm" + group_key_update = "1h" + passphrase = "AAAAAAAAA" + tls_certificate = "none" + tls_mode = "verify-certificate" + } + + depends_on = [ + routeros_capsman_channel.test_channel, + routeros_capsman_datapath.test_datapath, + routeros_capsman_rates.test_rates, + routeros_capsman_security.test_security + ] +}`, + } + return providerConfig + tests[n] +} diff --git a/routeros/resource_capsman_datapath.go b/routeros/resource_capsman_datapath.go new file mode 100644 index 00000000..4d98ce56 --- /dev/null +++ b/routeros/resource_capsman_datapath.go @@ -0,0 +1,117 @@ +package routeros + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +/* + { + ".id": "*1", + "arp": "disabled", + "bridge": "bridge", + "bridge-cost": "0", + "bridge-horizon": "none", + "client-to-client-forwarding": "true", + "interface-list": "all", + "l2mtu": "0", + "local-forwarding": "true", + "mtu": "32", + "name": "datapath1", + "vlan-id": "1", + "vlan-mode": "no-tag" + } +*/ + +// https://help.mikrotik.com/docs/display/ROS/CAPsMAN +func ResourceCapsManDatapath() *schema.Resource { + resSchema := map[string]*schema.Schema{ + MetaResourcePath: PropResourcePath("/caps-man/datapath"), + MetaId: PropId(Name), + + "arp": { + Type: schema.TypeString, + Optional: true, + Description: "ARP mode. See [docs](https://wiki.mikrotik.com/wiki/Manual:IP/ARP#ARP_Modes) for info.", + ValidateFunc: validation.StringInSlice([]string{"disabled", "enabled", "local-proxy-arp", "proxy-arp", + "reply-only"}, false), + }, + "bridge": { + Type: schema.TypeString, + Optional: true, + Description: "Bridge to which particular interface should be automatically added as port. Required " + + "only when local-forwarding is not used.", + }, + "bridge_cost": { + Type: schema.TypeInt, + Optional: true, + Description: "Bridge port cost to use when adding as bridge port.", + }, + "bridge_horizon": { + Type: schema.TypeInt, + Optional: true, + Description: "Bridge horizon to use when adding as bridge port.", + }, + "client_to_client_forwarding": { + Type: schema.TypeBool, + Optional: true, + Description: "Controls if client-to-client forwarding between wireless clients connected to interface " + + "should be allowed, in local forwarding mode this function is performed by CAP, otherwise it is " + + "performed by CAPsMAN.", + }, + KeyComment: PropCommentRw, + "interface_list": { + Type: schema.TypeString, + Optional: true, + Description: "Interface list name.", + }, + "l2mtu": { + Type: schema.TypeInt, + Optional: true, + Description: "Layer2 MTU size.", + }, + "local_forwarding": { + Type: schema.TypeBool, + Optional: true, + Description: "Controls forwarding mode. If disabled, all L2 and L3 data will be forwarded to CAPsMAN, " + + "and further forwarding decisions will be made only then. See [docs](https://wiki.mikrotik.com/wiki/Manual:CAPsMAN#Local_Forwarding_Mode) for info.", + }, + KeyName: PropNameRw, + "mtu": { + Type: schema.TypeInt, + Optional: true, + Description: "MTU size.", + }, + "openflow_switch": { + Type: schema.TypeString, + Optional: true, + Description: "OpenFlow switch to add interface to, as port when enabled.", + }, + "vlan_id": { + Type: schema.TypeInt, + Optional: true, + Description: "VLAN ID to assign to interface if vlan-mode enables use of VLAN tagging.", + ValidateFunc: validation.IntBetween(1, 4095), + }, + "vlan_mode": { + Type: schema.TypeString, + Optional: true, + Description: "VLAN tagging mode specifies if VLAN tag should be assigned to interface (causes all received " + + "data to get tagged with VLAN tag and allows interface to only send out data tagged with given tag)", + ValidateFunc: validation.StringInSlice([]string{"no-tag", "use-service-tag", "use-tag"}, false), + }, + } + + return &schema.Resource{ + CreateContext: DefaultCreate(resSchema), + ReadContext: DefaultRead(resSchema), + UpdateContext: DefaultUpdate(resSchema), + DeleteContext: DefaultDelete(resSchema), + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: resSchema, + } +} diff --git a/routeros/resource_capsman_datapath_test.go b/routeros/resource_capsman_datapath_test.go index 11fda66e..857ff9a2 100644 --- a/routeros/resource_capsman_datapath_test.go +++ b/routeros/resource_capsman_datapath_test.go @@ -25,7 +25,7 @@ func TestAccCapsManDatapathTest_basic(t *testing.T) { Config: testAccCapsManDatapathConfig(), Check: resource.ComposeTestCheckFunc( testAccCheckCapsManDatapathExists(testCapsManDatapathAddress), - resource.TestCheckResourceAttr(testInterfaceBridgeAddress, "name", "test_datapath"), + resource.TestCheckResourceAttr(testCapsManDatapathAddress, "name", "test_datapath"), ), }, }, @@ -50,15 +50,24 @@ func testAccCheckCapsManDatapathExists(name string) resource.TestCheckFunc { } func testAccCapsManDatapathConfig() string { - return ` - -provider "routeros" { - insecure = true -} + return providerConfig + ` resource "routeros_capsman_datapath" "test_datapath" { - name = "test_datapath" - } + name = "test_datapath" + comment = "test_datapath" + arp = "local-proxy-arp" + bridge = "bridge" + bridge_cost = 100 + bridge_horizon = 200 + client_to_client_forwarding = true + interface_list = "static" + l2mtu = 1450 + local_forwarding = true + mtu = 1500 + vlan_id = 101 + vlan_mode = "no-tag" +// openflow_switch = "aaa" +} ` } diff --git a/routeros/resource_capsman_manager.go b/routeros/resource_capsman_manager.go new file mode 100644 index 00000000..1923760d --- /dev/null +++ b/routeros/resource_capsman_manager.go @@ -0,0 +1,198 @@ +package routeros + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +/* /caps-man/manager +{ + "ca-certificate": "none", + "certificate": "none", + "enabled": "false", + "package-path": "", + "require-peer-certificate": "false", + "upgrade-policy": "none" +} +*/ + +// https://help.mikrotik.com/docs/display/ROS/CAPsMAN +func ResourceCapsManManager() *schema.Resource { + + resSchema := map[string]*schema.Schema{ + MetaResourcePath: PropResourcePath("/caps-man/manager"), + MetaId: PropId(Name), + + "ca_certificate": { + Type: schema.TypeString, + Optional: true, + Default: "none", + Description: "Device CA certificate.", + }, + "certificate": { + Type: schema.TypeString, + Optional: true, + Default: "none", + Description: "Device certificate.", + }, + "enabled": { + Type: schema.TypeBool, + Optional: true, + Description: "Disable or enable CAPsMAN functionality.", + }, + "package_path": { + Type: schema.TypeString, + Optional: true, + Description: "Folder location for the RouterOS packages. For example, use '/upgrade' to specify the " + + "upgrade folder from the files section. If empty string is set, CAPsMAN can use built-in RouterOS " + + "packages, note that in this case only CAPs with the same architecture as CAPsMAN will be upgraded.", + }, + "require_peer_certificate": { + Type: schema.TypeBool, + Optional: true, + Description: "Require all connecting CAPs to have a valid certificate.", + }, + "upgrade_policy": { + Type: schema.TypeString, + Optional: true, + Default: "none", + Description: "Upgrade policy options.", + ValidateFunc: validation.StringInSlice([]string{"none", "require-same-version", "suggest-same-upgrade"}, false), + }, + } + + return &schema.Resource{ + CreateContext: DefaultSystemCreate(resSchema), + ReadContext: DefaultSystemRead(resSchema), + UpdateContext: DefaultSystemUpdate(resSchema), + DeleteContext: DefaultSystemDelete(resSchema), + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: resSchema, + } +} + +/* /caps-man/manager/interface +{ + ".id": "*1", + "default": "true", + "disabled": "false", + "dynamic": "false", + "forbid": "false", + "interface": "all" +} +*/ + +// https://wiki.mikrotik.com/wiki/Manual:Simple_CAPsMAN_setup +func ResourceCapsManManagerInterface() *schema.Resource { + + resSchema := map[string]*schema.Schema{ + MetaResourcePath: PropResourcePath("/caps-man/manager/interface"), + MetaId: PropId(Id), + + KeyComment: PropCommentRw, + "default": { + Type: schema.TypeBool, + Computed: true, + }, + KeyDisabled: PropDisabledRw, + KeyDynamic: PropDynamicRo, + "forbid": { + Type: schema.TypeBool, + Optional: true, + Description: "Disable interface listening.", + }, + KeyInterface: PropInterfaceRw, + } + + return &schema.Resource{ + CreateContext: DefaultCreate(resSchema), + ReadContext: DefaultRead(resSchema), + UpdateContext: DefaultUpdate(resSchema), + DeleteContext: DefaultDelete(resSchema), + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: resSchema, + } +} + +/* /caps-man/aaa +{ + "called-format": "mac:ssid", + "interim-update": "disabled", + "mac-caching": "disabled", + "mac-format": "XX:XX:XX:XX:XX:XX", + "mac-mode": "as-username" +} +*/ + +// https://help.mikrotik.com/docs/display/ROS/CAPsMAN +func ResourceCapsManAaa() *schema.Resource { + + resSchema := map[string]*schema.Schema{ + MetaResourcePath: PropResourcePath("/caps-man/aaa"), + MetaId: PropId(Name), + + "called_format": { + Type: schema.TypeString, + Optional: true, + Default: "mac:ssid", + Description: "Format of how the 'called-id' identifier will be passed to RADIUS. When configuring radius " + + "server clients, you can specify 'called-id' in order to separate multiple entires.", + ValidateFunc: validation.StringInSlice([]string{"mac", "mac:ssid", "ssid"}, false), + }, + "interim_update": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "When RADIUS accounting is used, Access Point periodically sends accounting information " + + "updates to the RADIUS server. This property specifies the default update interval that can be " + + "overridden by the RADIUS server using the Acct-Interim-Interval attribute.", + //DiffSuppressFunc: TimeEquall, // "interim-update": "disabled" + }, + "mac_caching": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "If this value is set to a time interval, the Access Point will cache RADIUS MAC authentication " + + "responses for a specified time, and will not contact the RADIUS server if matching cache entry already " + + "exists. The value disabled will disable the cache, Access Point will always contact the RADIUS server.", + //DiffSuppressFunc: TimeEquall, // "mac-caching": "disabled" + }, + "mac_format": { + Type: schema.TypeString, + Optional: true, + Default: "XX:XX:XX:XX:XX:XX", + Description: "Controls how the MAC address of the client is encoded by Access Point in the User-Name " + + "attribute of the MAC authentication and MAC accounting RADIUS requests.", + }, + "mac_mode": { + Type: schema.TypeString, + Optional: true, + Default: "as-username", + Description: "By default Access Point uses an empty password, when sending Access-Request during MAC " + + "authentication. When this property is set to as-username-and-password, Access Point will use the same " + + "value for the User-Password attribute as for the User-Name attribute.", + ValidateFunc: validation.StringInSlice([]string{"as-username", "as-username-and-password"}, false), + }, + } + + return &schema.Resource{ + CreateContext: DefaultSystemCreate(resSchema), + ReadContext: DefaultSystemRead(resSchema), + UpdateContext: DefaultSystemUpdate(resSchema), + DeleteContext: DefaultSystemDelete(resSchema), + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: resSchema, + } +} diff --git a/routeros/resource_capsman_manager_test.go b/routeros/resource_capsman_manager_test.go new file mode 100644 index 00000000..08d8dd8c --- /dev/null +++ b/routeros/resource_capsman_manager_test.go @@ -0,0 +1,125 @@ +package routeros + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +const testCapsManAaaAddress = "routeros_capsman_aaa.test_3a" +const testCapsManManagerAddress = "routeros_capsman_manager.test_manager" +const testCapsManManagerInterfaceAddress = "routeros_capsman_manager_interface.test_manager_interface" + +func TestAccCapsManManagerTest_basic(t *testing.T) { + for _, name := range testNames { + t.Run(name, func(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testSetTransportEnv(t, name) + }, + ProviderFactories: testAccProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccCapsManManagerUnitConfig(name, "routeros_capsman_manager"), + Check: resource.ComposeTestCheckFunc( + testAccCheckCapsManResourceExists(testCapsManManagerAddress), + resource.TestCheckResourceAttr(testCapsManManagerAddress, "id", "caps-man.manager"), + ), + }, + { + Config: testAccCapsManManagerUnitConfig(name, "routeros_capsman_aaa"), + Check: resource.ComposeTestCheckFunc( + testAccCheckCapsManResourceExists(testCapsManAaaAddress), + resource.TestCheckResourceAttr(testCapsManAaaAddress, "id", "caps-man.aaa"), + ), + }, + { + Config: testAccCapsManManagerUnitConfig(name, "routeros_capsman_manager_interface"), + Check: resource.ComposeTestCheckFunc( + testAccCheckCapsManResourceExists(testCapsManManagerInterfaceAddress), + resource.TestCheckResourceAttr(testCapsManManagerInterfaceAddress, "interface", "ether1"), + ), + }, + }, + }) + }) + } +} + +func testAccCheckCapsManResourceExists(address string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[address] + if !ok { + return fmt.Errorf("not found: %s", address) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("no id is set") + } + + return nil + } +} + +func testAccCapsManManagerUnitConfig(testName, resourceName string) string { + conf := ` + provider "routeros" { + insecure = true + } +` + + switch resourceName { + // AAA + case "routeros_capsman_aaa": + if strings.Contains(testName, "API") { + // API + conf += ` + resource "routeros_capsman_aaa" "test_3a" { + called_format = "ssid" + mac_mode = "as-username-and-password" + } + ` + } else { + // REST + conf += ` + resource "routeros_capsman_aaa" "test_3a" { + called_format = "mac:ssid" + mac_mode = "as-username" + } + ` + } + // Manager + case "routeros_capsman_manager": + if strings.Contains(testName, "API") { + // API + conf += ` + resource "routeros_capsman_manager" "test_manager" { + enabled = true + upgrade_policy = "require-same-version" + } + ` + } else { + // REST + conf += ` + resource "routeros_capsman_manager" "test_manager" { + enabled = false + upgrade_policy = "none" + } + ` + } + // CAPsMAN interfaces + case "routeros_capsman_manager_interface": + conf += ` + resource "routeros_capsman_manager_interface" "test_manager_interface" { + interface = "ether1" + forbid = true + } + ` + } + + return conf +} diff --git a/routeros/resource_capsman_provisioning.go b/routeros/resource_capsman_provisioning.go new file mode 100644 index 00000000..fae04cdc --- /dev/null +++ b/routeros/resource_capsman_provisioning.go @@ -0,0 +1,108 @@ +package routeros + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +/* + { + ".id": "*1", + "action": "none", + "common-name-regexp": "", + "disabled": "false", + "hw-supported-modes": "", + "identity-regexp": "", + "ip-address-ranges": "", + "master-configuration": "cfg1", + "name-format": "cap", + "name-prefix": "", + "radio-mac": "00:00:00:00:00:00", + "slave-configurations": "" + } +*/ + +// https://help.mikrotik.com/docs/display/ROS/CAPsMAN +func ResourceCapsManProvisioning() *schema.Resource { + resSchema := map[string]*schema.Schema{ + MetaResourcePath: PropResourcePath("/caps-man/provisioning"), + MetaId: PropId(Id), + + "action": { + Type: schema.TypeString, + Optional: true, + Default: "none", + Description: "Provisioning action.", + ValidateFunc: validation.StringInSlice([]string{"create-disabled", "create-enabled", + "create-dynamic-enabled", "none"}, false), + }, + KeyComment: PropCommentRw, + "common_name_regexp": { + Type: schema.TypeString, + Optional: true, + Description: "Regular expression to match radios by common name. Each CAP's common name identifier can be " + + `found under "/caps-man radio" as value "REMOTE-CAP-NAME"`, + }, + KeyDisabled: PropDisabledRw, + "hw_supported_modes": { + Type: schema.TypeString, + Optional: true, + Description: "Match radios by supported wireless modes.", + ValidateFunc: validation.StringInSlice([]string{"a", "a-turbo", "ac", "an", "b", "g", "g-turbo", "gn"}, false), + }, + "identity_regexp": { + Type: schema.TypeString, + Optional: true, + Description: "Regular expression to match radios by router identity.", + }, + "ip_address_ranges": { + Type: schema.TypeString, + Optional: true, + Description: "Match CAPs with IPs within configured address range.", + }, + "master_configuration": { + Type: schema.TypeString, + Required: true, + Description: "If action specifies to create interfaces, then a new master interface with its configuration " + + "set to this configuration profile will be created", + }, + "name_format": { + Type: schema.TypeString, + Optional: true, + Default: "cap", + Description: "Specify the syntax of the CAP interface name creation.", + ValidateFunc: validation.StringInSlice([]string{"cap", "identity", "prefix", "prefix-identity"}, false), + }, + "name_prefix": { + Type: schema.TypeString, + Optional: true, + Description: "Name prefix which can be used in the name-format for creating the CAP interface names.", + }, + "radio_mac": { + Type: schema.TypeString, + Optional: true, + Default: "00:00:00:00:00:00", + Description: "MAC address of radio to be matched, empty MAC (00:00:00:00:00:00) means match all MAC addresses.", + ValidateFunc: ValidationMacAddress, + }, + "slave_configurations": { + Type: schema.TypeString, + Optional: true, + Description: "If action specifies to create interfaces, then a new slave interface for each configuration " + + "profile in this list is created.", + }, + } + + return &schema.Resource{ + CreateContext: DefaultCreate(resSchema), + ReadContext: DefaultRead(resSchema), + UpdateContext: DefaultUpdate(resSchema), + DeleteContext: DefaultDelete(resSchema), + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: resSchema, + } +} diff --git a/routeros/resource_capsman_provisioning_test.go b/routeros/resource_capsman_provisioning_test.go index eb3aca89..a6c997b1 100644 --- a/routeros/resource_capsman_provisioning_test.go +++ b/routeros/resource_capsman_provisioning_test.go @@ -25,7 +25,7 @@ func TestAccCapsManProvisioningTest_basic(t *testing.T) { Config: testAccCapsManProvisioningConfig(), Check: resource.ComposeTestCheckFunc( testAccCheckCapsManProvisioningExists(testCapsManProvisioningAddress), - resource.TestCheckResourceAttr(testCapsManProvisioningAddress, "name", "test_provisioning"), + resource.TestCheckResourceAttr(testCapsManProvisioningAddress, "name_prefix", "cap-"), ), }, }, @@ -51,15 +51,21 @@ func testAccCheckCapsManProvisioningExists(name string) resource.TestCheckFunc { } func testAccCapsManProvisioningConfig() string { - return ` + return providerConfig + ` -provider "routeros" { - insecure = true +resource "routeros_capsman_configuration" "test_configuration" { + name = "cfg1" } resource "routeros_capsman_provisioning" "test_provisioning" { - action = "create-disabled" - } + master_configuration = "cfg1" + action = "create-disabled" + name_prefix = "cap-" + + depends_on = [ + routeros_capsman_configuration.test_configuration, + ] +} ` } diff --git a/routeros/resource_capsman_rates.go b/routeros/resource_capsman_rates.go new file mode 100644 index 00000000..fb400a61 --- /dev/null +++ b/routeros/resource_capsman_rates.go @@ -0,0 +1,113 @@ +package routeros + +import ( + "regexp" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +/* + { + ".id": "*1", + "basic": "1Mbps,5.5Mbps,6Mbps,18Mbps,36Mbps,54Mbps", + "ht-basic-mcs": "mcs-0,mcs-7,mcs-11,mcs-14,mcs-16,mcs-21", + "ht-supported-mcs": "mcs-3,mcs-8,mcs-10,mcs-13,mcs-17,mcs-18", + "name": "rate-cfg", + "supported": "2Mbps,11Mbps,9Mbps,12Mbps,24Mbps,48Mbps", + "vht-basic-mcs": "none", + "vht-supported-mcs": "" + } +*/ + +// https://help.mikrotik.com/docs/display/ROS/CAPsMAN +func ResourceCapsManRates() *schema.Resource { + resSchema := map[string]*schema.Schema{ + MetaResourcePath: PropResourcePath("/caps-man/rates"), + MetaId: PropId(Name), + + "basic": { + Type: schema.TypeSet, + Optional: true, + Description: "List of basic rates. Client will connect to AP only if it supports all basic " + + "rates announced by the AP. AP will establish WDS link only if it supports all basic " + + "rates of the other AP.", + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice([]string{"1Mbps", "2Mbps", "5.5Mbps", "6Mbps", + "9Mbps", "11Mbps", "12Mbps", "18Mbps", "24Mbps", "36Mbps", "48Mbps", "54Mbps"}, false), + }, + }, + "supported": { + Type: schema.TypeSet, + Optional: true, + Description: "List of supported rates. Two devices will communicate only using rates that " + + "are supported by both devices.", + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice([]string{"1Mbps", "2Mbps", "5.5Mbps", "6Mbps", + "9Mbps", "11Mbps", "12Mbps", "18Mbps", "24Mbps", "36Mbps", "48Mbps", "54Mbps"}, false), + }, + }, + KeyComment: PropCommentRw, + "ht_basic_mcs": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Description: "Modulation and Coding Schemes that every connecting client must support. Refer to " + + "802.11n for MCS specification.", + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringMatch(regexp.MustCompile(`mcs-\d+`), + `ht_basic_mcs format is "mcs-[0..23]": mcs-"12"`), + }, + }, + "ht_supported_mcs": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Description: "Modulation and Coding Schemes that this device advertises as supported. Refer to 802.11n " + + "for MCS specification.", + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringMatch(regexp.MustCompile(`mcs-\d+`), + `ht_supported_mcs format is "mcs-[0..23]": "mcs-11"`), + }, + }, + KeyName: PropNameRw, + "vht_basic_mcs": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "Modulation and Coding Schemes that every connecting client must support. Refer to " + + "802.11ac for MCS specification. You can set MCS interval for each of Spatial Stream none " + + "- will not use selected Spatial Stream MCS 0-7 - client must support MCS-0 to MCS-7 MCS " + + "0-8 - client must support MCS-0 to MCS-8 MCS 0-9 - client must support MCS-0 to MCS-9", + ValidateDiagFunc: ValidationMultiValInSlice([]string{"none", "mcs0-7", "mcs0-8", "mcs0-9"}, false, false), + }, + "vht_supported_mcs": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "Modulation and Coding Schemes that this device advertises as supported. Refer to " + + "802.11ac for MCS specification. You can set MCS interval for each of Spatial Stream none " + + "- will not use selected Spatial Stream MCS 0-7 - devices will advertise as supported " + + "MCS-0 to MCS-7 MCS 0-8 - devices will advertise as supported MCS-0 to MCS-8 MCS 0-9 - " + + "devices will advertise as supported MCS-0 to MCS-9", + ValidateDiagFunc: ValidationMultiValInSlice([]string{"none", "mcs0-7", "mcs0-8", "mcs0-9"}, false, false), + }, + } + + return &schema.Resource{ + CreateContext: DefaultCreate(resSchema), + ReadContext: DefaultRead(resSchema), + UpdateContext: DefaultUpdate(resSchema), + DeleteContext: DefaultDelete(resSchema), + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: resSchema, + } +} diff --git a/routeros/resource_capsman_rates_test.go b/routeros/resource_capsman_rates_test.go new file mode 100644 index 00000000..23002f89 --- /dev/null +++ b/routeros/resource_capsman_rates_test.go @@ -0,0 +1,67 @@ +package routeros + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +const testCapsManRatesAddress = "routeros_capsman_rates.test_rates" + +func TestAccCapsManRatesTest_basic(t *testing.T) { + for _, name := range testNames { + t.Run(name, func(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testSetTransportEnv(t, name) + }, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testCheckResourceDestroy("/caps-man/rates", "routeros_capsman_rates"), + Steps: []resource.TestStep{ + { + Config: testAccCapsManRatesConfig(), + Check: resource.ComposeTestCheckFunc( + testAccCheckCapsManRatesExists(testCapsManRatesAddress), + resource.TestCheckResourceAttr(testCapsManRatesAddress, "name", "test_rates"), + ), + }, + }, + }) + + }) + } +} + +func testAccCheckCapsManRatesExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("not found: %s", name) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("no id is set") + } + + return nil + } +} + +func testAccCapsManRatesConfig() string { + return providerConfig + ` + +resource "routeros_capsman_rates" "test_rates" { + name = "test_rates" + comment = "test_rates" + basic = ["1Mbps", "5.5Mbps", "6Mbps", "18Mbps", "36Mbps", "54Mbps"] + ht_basic_mcs = ["mcs-0", "mcs-7", "mcs-11", "mcs-14", "mcs-16", "mcs-21"] + ht_supported_mcs = ["mcs-3", "mcs-8", "mcs-10", "mcs-13", "mcs-17", "mcs-18"] + supported = ["2Mbps", "11Mbps", "9Mbps", "12Mbps", "24Mbps", "48Mbps"] + vht_basic_mcs = "none" + vht_supported_mcs = "mcs0-9,mcs0-7" +} +` +} diff --git a/routeros/resource_capsman_security.go b/routeros/resource_capsman_security.go new file mode 100644 index 00000000..6f2d7eef --- /dev/null +++ b/routeros/resource_capsman_security.go @@ -0,0 +1,124 @@ +package routeros + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +/* + { + ".id": "*1", + "authentication-types": "", + "disable-pmkid": "false", + "eap-methods": "eap-tls", + "eap-radius-accounting": "false", + "encryption": "", + "group-encryption": "aes-ccm", + "group-key-update": "1m", + "name": "security1", + "passphrase": "123123123", + "tls-certificate": "none", + "tls-mode": "verify-certificate" + } +*/ + +// https://help.mikrotik.com/docs/display/ROS/CAPsMAN +func ResourceCapsManSecurity() *schema.Resource { + + resSchema := map[string]*schema.Schema{ + MetaResourcePath: PropResourcePath("/caps-man/security"), + MetaId: PropId(Name), + + "authentication_types": { + Type: schema.TypeSet, + Optional: true, + Description: "Specify the type of Authentication from wpa-psk, wpa2-psk, wpa-eap or wpa2-eap.", + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateDiagFunc: ValidationMultiValInSlice([]string{"wpa-psk", "wpa2-psk", "wpa-eap", "wpa2-eap"}, + false, false), + }, + }, + KeyComment: PropCommentRw, + "disable_pmkid": { + Type: schema.TypeBool, + Optional: true, + Description: "Whether to include PMKID into the EAPOL frame sent out by the Access Point. Disabling PMKID " + + "can cause compatibility issues with devices that use the PMKID to connect to an Access Point.", + }, + "eap_methods": { + Type: schema.TypeString, + Optional: true, + Description: "eap-tls - Use built-in EAP TLS authentication; passthrough - Access point will relay " + + "authentication process to the RADIUS server.", + ValidateDiagFunc: ValidationMultiValInSlice([]string{"eap-tls", "passthrough"}, false, false), + }, + "eap_radius_accounting": { + Type: schema.TypeBool, + Optional: true, + Description: "Specifies if RADIUS traffic accounting should be used if RADIUS authentication gets done for " + + "this client", + }, + "encryption": { + Type: schema.TypeSet, + Optional: true, + Description: "Set type of unicast encryption algorithm used.", + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateDiagFunc: ValidationMultiValInSlice([]string{"aes-ccm", "tkip"}, false, false), + }, + }, + "group_encryption": { + Type: schema.TypeString, + Optional: true, + Description: "Access Point advertises one of these ciphers, multiple values can be selected. Access Point " + + "uses it to encrypt all broadcast and multicast frames. Client attempts connection only to Access " + + "Points that use one of the specified group ciphers.", + ValidateFunc: validation.StringInSlice([]string{"aes-ccm", "tkip"}, false), + }, + "group_key_update": { + Type: schema.TypeString, + Optional: true, + Description: "Controls how often Access Point updates the group key. This key is used to encrypt all " + + "broadcast and multicast frames. property only has effect for Access Points. (30s..1h)", + DiffSuppressFunc: TimeEquall, + }, + KeyName: PropNameRw, + // 802.11i specification: + // A pass-phrase is a sequence of between 8 and 63 ASCII-encoded characters. The limit of 63 comes from the + // desire to distinguish between a pass-phrase and a PSK displayed as 64 hexadecimal characters. + "passphrase": { + Type: schema.TypeString, + Optional: true, + Description: "WPA or WPA2 pre-shared key.", + Sensitive: true, + ValidateFunc: validation.StringLenBetween(8, 63), + }, + "tls_certificate": { + Type: schema.TypeString, + Optional: true, + Description: "Access Point always needs a certificate when security.tls-mode is set to value other than " + + "no-certificates.", + }, + "tls_mode": { + Type: schema.TypeString, + Optional: true, + Description: "This property has effect only when security.eap-methods contains eap-tls.", + ValidateFunc: validation.StringInSlice([]string{"verify-certificate", "dont-verify-certificate", + "no-certificates", "verify-certificate-with-crl"}, false), + }, + } + + return &schema.Resource{ + CreateContext: DefaultCreate(resSchema), + ReadContext: DefaultRead(resSchema), + UpdateContext: DefaultUpdate(resSchema), + DeleteContext: DefaultDelete(resSchema), + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: resSchema, + } +} diff --git a/routeros/resource_capsman_security_test.go b/routeros/resource_capsman_security_test.go index 17df76f9..de34e3f4 100644 --- a/routeros/resource_capsman_security_test.go +++ b/routeros/resource_capsman_security_test.go @@ -51,15 +51,21 @@ func testAccCheckCapsManSecurityExists(name string) resource.TestCheckFunc { } func testAccCapsManSecurityConfig() string { - return ` - -provider "routeros" { - insecure = true -} + return providerConfig + ` resource "routeros_capsman_security" "test_security" { - name = "test_security" - } - + name = "test_security" + comment = "test_security" + authentication_types = ["wpa-psk", "wpa-eap", "wpa2-psk"] // Unordered items! + disable_pmkid = true + eap_methods = "eap-tls,passthrough" + eap_radius_accounting = true + encryption = ["tkip", "aes-ccm"] // Unordered items! + group_encryption = "aes-ccm" + group_key_update = "1h" + passphrase = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE" // Max length check + tls_certificate = "none" + tls_mode = "verify-certificate" +} ` }