Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add resource routeros_ipv6_neighbor_discovery #362

Merged
merged 3 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#The ID can be found via API or the terminal
#The command for the terminal is -> :put [/ipv6/nd get [print show-ids]]
terraform import routeros_ipv6_neighbor_discovery.ndether1 "*0"
19 changes: 19 additions & 0 deletions examples/resources/routeros_ipv6_neighbor_discovery/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@

resource "routeros_ipv6_neighbor_discovery" "test" {
interface = "ether1"
hop_limit = 33
advertise_dns = false
advertise_mac_address = true
disabled = false
managed_address_configuration = true
mtu = 9000
other_configuration = true
pref64_prefixes = []
ra_delay = "3s"
ra_interval = "3m20s-10m"
ra_lifetime = "30m"
ra_preference = "high"
reachable_time = "10m"
retransmit_interval = "12m"
}
`
20 changes: 18 additions & 2 deletions routeros/mikrotik_serialize.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@ func loadSkipFields(s string) (m map[string]struct{}) {
return
}

func loadDropByValue(s string) (m map[string]struct{}) {
m = make(map[string]struct{})
for _, value := range strings.Split(s, ",") {
m[value] = struct{}{}
}
return m
}

// ListToString Convert List and Set to a delimited string.
func ListToString(v any) (res string) {
for i, elem := range v.([]interface{}) {
Expand Down Expand Up @@ -139,7 +147,7 @@ func TerraformResourceDataToMikrotik(s map[string]*schema.Schema, d *schema.Reso
meta.IdType = IdType(terraformMetadata.Default.(int))
case MetaResourcePath:
meta.Path = terraformMetadata.Default.(string)
case MetaTransformSet, MetaSkipFields, MetaSetUnsetFields:
case MetaTransformSet, MetaSkipFields, MetaSetUnsetFields, MetaDropByValue:
continue
default:
meta.Meta[terraformSnakeName] = terraformMetadata.Default.(string)
Expand Down Expand Up @@ -290,7 +298,7 @@ func MikrotikResourceDataToTerraform(item MikrotikItem, s map[string]*schema.Sch
var diags diag.Diagnostics
var err error
var transformSet map[string]string
var setUnsetFields, skipFields map[string]struct{}
var setUnsetFields, skipFields, dropByValue map[string]struct{}

// {"channel": "channel.config", "mikrotik-field-name": "schema-field-name"}
if ts, ok := s[MetaTransformSet]; ok {
Expand All @@ -305,6 +313,10 @@ func MikrotikResourceDataToTerraform(item MikrotikItem, s map[string]*schema.Sch
skipFields = loadSkipFields(sf.Default.(string))
}

if dbv, ok := s[MetaDropByValue]; ok {
dropByValue = loadDropByValue(dbv.Default.(string))
}

// TypeMap,TypeSet initialization information storage.
var maps = make(map[string]map[string]interface{})
var nestedLists = make(map[string]map[string]interface{})
Expand All @@ -324,6 +336,10 @@ func MikrotikResourceDataToTerraform(item MikrotikItem, s map[string]*schema.Sch
continue
}

if _, ok := dropByValue[mikrotikValue]; ok {
continue
}

// Mikrotik fields transformation: "channel" ---> "channel.config".
// For further use in the map.
if transformSet != nil {
Expand Down
1 change: 1 addition & 0 deletions routeros/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ func Provider() *schema.Provider {
"routeros_ipv6_dhcp_client_option": ResourceIPv6DhcpClientOption(),
"routeros_ipv6_firewall_addr_list": ResourceIPv6FirewallAddrList(),
"routeros_ipv6_firewall_filter": ResourceIPv6FirewallFilter(),
"routeros_ipv6_neighbor_discovery": ResourceIPv6NeighborDiscovery(),
"routeros_ipv6_route": ResourceIPv6Route(),

// Aliases for IP objects to retain compatibility between original and fork
Expand Down
13 changes: 13 additions & 0 deletions routeros/provider_schema_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const (
MetaTransformSet = "___ts___"
MetaSkipFields = "___skip___"
MetaSetUnsetFields = "___unset___"
MetaDropByValue = "___drop_val___"
)

const (
Expand Down Expand Up @@ -79,6 +80,18 @@ func PropId(t IdType) *schema.Schema {
}
}

func PropDropByValue(values ...string) *schema.Schema {
return &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: strings.Join(values, ","),
Description: "<em>A list of values when generated by RouterOs will be dropped, useful to default values as 'unspecified' for null</em>",
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
return true
},
}
}

// PropTransformSet
func PropTransformSet(s string) *schema.Schema {
return &schema.Schema{
Expand Down
2 changes: 1 addition & 1 deletion routeros/resource_interface_ethernet.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func ResourceInterfaceEthernet() *schema.Resource {
`"tx_broadcast","tx_bytes","tx_control","tx_drop","tx_fcs_error","tx_fragment","tx_jabber","tx_multicast","tx_packet","tx_pause","tx_too_short","tx_too_long",` +
`"rx_align_error","rx_carrier_error","rx_code_error","rx_error_events","rx_length_error","rx_overflow","rx_unicast","rx_unknown_op"` +
`"tx_collision","tx_excessive_collision","tx_late_collision","tx_multiple_collision","tx_single_collision","tx_total_collision",` +
`"tx_deferred","tx_excessive_deferred","tx_unicast","tx_underrun",`,
`"tx_deferred","tx_excessive_deferred","tx_unicast","tx_underrun"`,
),

"advertise": {
Expand Down
153 changes: 153 additions & 0 deletions routeros/resource_ipv6_neighbor_discovery.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package routeros

import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)

/*
[
{
".id": "*3",
"advertise-dns": "false",
"advertise-mac-address": "true",
"default": "false",
"disabled": "false",
"dns": "",
"hop-limit": "unspecified",
"interface": "vlan-wifi-XXX",
"invalid": "false",
"managed-address-configuration": "true",
"mtu": "unspecified",
"other-configuration": "true",
"pref64": "",
"ra-delay": "3s",
"ra-interval": "3m20s-10m",
"ra-lifetime": "30m",
"ra-preference": "high",
"reachable-time": "unspecified",
"retransmit-interval": "unspecified"
}
]
*/

// ResourceIPv6NeighborDiscovery https://help.mikrotik.com/docs/display/ROS/IPv6+Neighbor+Discovery
func ResourceIPv6NeighborDiscovery() *schema.Resource {
resSchema := map[string]*schema.Schema{
MetaResourcePath: PropResourcePath("/ipv6/nd"),
MetaId: PropId(Id),
MetaDropByValue: PropDropByValue("unspecified"),
"advertise_dns": {
Type: schema.TypeBool,
Optional: true,
Description: "Option to redistribute DNS server information using RADVD. You will need a running client-side software with Router Advertisement DNS support to take advantage of the advertised DNS information.",
Default: true,
},
"advertise_mac_address": {
Type: schema.TypeBool,
Optional: true,
Description: "When set, the link-layer address of the outgoing interface is included in the RA.",
Default: true,
},
KeyComment: PropCommentRw,
"dns_servers": {
Type: schema.TypeString,
Optional: true,
Description: "Specify a single IPv6 address or list of addresses that will be provided to hosts for DNS server configuration.",
ValidateFunc: validation.IsCIDR,
},
KeyDisabled: PropDisabledRw,
"hop_limit": {
Type: schema.TypeInt,
Optional: true,
Description: "The default value that should be placed in the Hop Count field of the IP header for outgoing (unicast) IP packets.",
ValidateFunc: validation.IntBetween(0, 255),
},
"interface": {
Type: schema.TypeString,
Required: true,
Description: "The interface on which to run neighbor discovery." +
"all - run ND on all running interfaces.",
},
"managed_address_configuration": {
Type: schema.TypeBool,
Optional: true,
Description: "Name of the IPv6 pool in which received IPv6 prefix will be added",
},
"mtu": {
Type: schema.TypeInt,
Optional: true,
Description: "The flag indicates whether hosts should use stateful autoconfiguration (DHCPv6) to obtain addresses",
ValidateFunc: validation.IntBetween(0, 4294967295),
},
"other_configuration": {
Type: schema.TypeBool,
Optional: true,
Description: "The flag indicates whether hosts should use stateful autoconfiguration to obtain additional information (excluding addresses).",
},
"pref64_prefixes": {
Type: schema.TypeList,
Optional: true,
Description: "Specify IPv6 prefix or list of prefixes within /32, /40. /48, /56, /64, or /96 subnet that will be provided to hosts as NAT64 prefixes.",
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.IsCIDR,
},
DiffSuppressFunc: AlwaysPresentNotUserProvided,
},
"ra_delay": {
Type: schema.TypeString,
Optional: true,
Description: "The minimum time allowed between sending multicast router advertisements from the interface.",
Default: "3s",
},
"ra_interval": {
Type: schema.TypeString,
Optional: true,
Description: "The min-max interval allowed between sending unsolicited multicast router advertisements from the interface.",
Default: "3m20s-10m",
},
"ra_preference": {
Type: schema.TypeString,
Optional: true,
Description: "Specify the router preference that is communicated to IPv6 hosts through router advertisements." +
"The preference value in the router advertisements enables IPv6 hosts to select a default router to reach a remote destination",
Default: "medium",
ValidateFunc: validation.StringInSlice([]string{"low", "medium", "high"}, false),
},
"ra_lifetime": {
Type: schema.TypeString,
Optional: true,
Description: "Specify the router preference that is communicated to IPv6 hosts through router advertisements." +
"The preference value in the router advertisements enables IPv6 hosts to select a default router to reach a remote destination",
Default: "30m",
},
"reachable_time": {
Type: schema.TypeString,
Optional: true,
Description: "Specify the router preference that is communicated to IPv6 hosts through router advertisements." +
"The preference value in the router advertisements enables IPv6 hosts to select a default router to reach a remote destination",
DiffSuppressFunc: AlwaysPresentNotUserProvided,
},
"retransmit_interval": {
Type: schema.TypeString,
Optional: true,
Description: "The time between retransmitted Neighbor Solicitation messages." +
"Used by address resolution and the Neighbor Unreachability Detection algorithm (see Sections 7.2 and 7.3 of RFC 2461)",
DiffSuppressFunc: func(k, oldValue, newValue string, d *schema.ResourceData) bool {
Copy link
Collaborator

@vaerh vaerh Feb 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found the problem. It is contained in DiffSuppressFunc.
JSON generation in the current implementation:

'retransmit-interval':''
'interface':'ether1'
'ra-delay':'3s'
'ra-interval':'3m20s-10m'
'ra-lifetime':'30m'
'reachable-time':'10m'
'ra-preference':'high'

JSON with our nice and working AlwaysPresentNotUserProvided function

'retransmit-interval':'12m'
'interface':'ether1'
'ra-delay':'3s'
'ra-interval':'3m20s-10m'
'reachable-time':'10m'
'ra-preference':'high'
'ra-lifetime':'30m'

We should remove all such functions if they give such problems. If I remember correctly, we used them somewhere....

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you very much... I left this here by accident while I was testing something. My apologies, I'll fix it soon.

return true
},
},
}
return &schema.Resource{
CreateContext: DefaultCreate(resSchema),
ReadContext: DefaultRead(resSchema),
UpdateContext: DefaultUpdate(resSchema),
DeleteContext: DefaultDelete(resSchema),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},

Schema: resSchema,
}
}
102 changes: 102 additions & 0 deletions routeros/resource_ipv6_neighbor_discovery_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package routeros

import (
"testing"

"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)

const testIPv6NeighborDiscoveryAddress = "routeros_ipv6_neighbor_discovery.test"

func TestAccIPv6FNeighborDiscoveryTest_full(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("/ipv6/nd", "routeros_ipv6_neighbor_discovery"),
Steps: []resource.TestStep{
{
Config: testAccFullIPv6NeighborDiscoveryConfig(),
Check: resource.ComposeTestCheckFunc(
testResourcePrimaryInstanceId(testIPv6NeighborDiscoveryAddress),
resource.TestCheckResourceAttr(testIPv6NeighborDiscoveryAddress, "interface", "ether1"),
resource.TestCheckResourceAttr(testIPv6NeighborDiscoveryAddress, "hop_limit", "33"),
resource.TestCheckResourceAttr(testIPv6NeighborDiscoveryAddress, "advertise_dns", "false"),
resource.TestCheckResourceAttr(testIPv6NeighborDiscoveryAddress, "advertise_mac_address", "true"),
resource.TestCheckResourceAttr(testIPv6NeighborDiscoveryAddress, "disabled", "false"),
resource.TestCheckResourceAttr(testIPv6NeighborDiscoveryAddress, "managed_address_configuration", "true"),
resource.TestCheckResourceAttr(testIPv6NeighborDiscoveryAddress, "mtu", "9000"),
resource.TestCheckResourceAttr(testIPv6NeighborDiscoveryAddress, "other_configuration", "true"),
resource.TestCheckResourceAttr(testIPv6NeighborDiscoveryAddress, "ra_delay", "3s"),
resource.TestCheckResourceAttr(testIPv6NeighborDiscoveryAddress, "ra_interval", "3m20s-10m"),
resource.TestCheckResourceAttr(testIPv6NeighborDiscoveryAddress, "ra_lifetime", "30m"),
resource.TestCheckResourceAttr(testIPv6NeighborDiscoveryAddress, "ra_preference", "high"),
resource.TestCheckResourceAttr(testIPv6NeighborDiscoveryAddress, "reachable_time", "10m"),
resource.TestCheckResourceAttr(testIPv6NeighborDiscoveryAddress, "retransmit_interval", "12m"),
),
},
},
})
})
}
}

func TestAccIPv6FNeighborDiscoveryTest_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("/ipv6/nd", "routeros_ipv6_neighbor_discovery"),
Steps: []resource.TestStep{
{
Config: testAccSimpleIPv6NeighborDiscoveryConfig(),
Check: resource.ComposeTestCheckFunc(
testResourcePrimaryInstanceId(testIPv6NeighborDiscoveryAddress),
resource.TestCheckResourceAttr(testIPv6NeighborDiscoveryAddress, "interface", "ether1"),
),
},
},
})
})
}
}

func testAccFullIPv6NeighborDiscoveryConfig() string {
return providerConfig + `

resource "routeros_ipv6_neighbor_discovery" "test" {
interface = "ether1"
hop_limit = 33
advertise_dns = false
advertise_mac_address = true
disabled = false
managed_address_configuration = true
mtu = 9000
other_configuration = true
pref64_prefixes = []
ra_delay = "3s"
ra_interval = "3m20s-10m"
ra_lifetime = "30m"
ra_preference = "high"
reachable_time = "10m"
retransmit_interval = "12m"
}
`
}

func testAccSimpleIPv6NeighborDiscoveryConfig() string {
return providerConfig + `

resource "routeros_ipv6_neighbor_discovery" "test" {
interface = "ether1"
}
`
}
Loading