diff --git a/examples/resources/routeros_ip_cloud/import.sh b/examples/resources/routeros_ip_cloud/import.sh new file mode 100644 index 00000000..62c5831b --- /dev/null +++ b/examples/resources/routeros_ip_cloud/import.sh @@ -0,0 +1 @@ +terraform import routeros_ip_cloud.test . diff --git a/examples/resources/routeros_ip_cloud/resource.tf b/examples/resources/routeros_ip_cloud/resource.tf new file mode 100644 index 00000000..09dd0d87 --- /dev/null +++ b/examples/resources/routeros_ip_cloud/resource.tf @@ -0,0 +1,5 @@ +resource "routeros_ip_cloud" "test" { + ddns_enabled = true + update_time = false + ddns_update_interval = "11m" +} \ No newline at end of file diff --git a/routeros/provider.go b/routeros/provider.go index 56c2d57d..40e6648b 100644 --- a/routeros/provider.go +++ b/routeros/provider.go @@ -123,6 +123,7 @@ func Provider() *schema.Provider { "routeros_wireguard_peer": ResourceInterfaceWireguardPeer(), // System Objects + "routeros_ip_cloud": ResourceIpCloud(), "routeros_system_identity": ResourceSystemIdentity(), "routeros_system_scheduler": ResourceSystemScheduler(), "routeros_system_certificate": ResourceSystemCertificate(), diff --git a/routeros/resource_ip_cloud.go b/routeros/resource_ip_cloud.go new file mode 100644 index 00000000..73bdf59d --- /dev/null +++ b/routeros/resource_ip_cloud.go @@ -0,0 +1,113 @@ +package routeros + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +/* +{ + "ddns-enabled": "true", + "ddns-update-interval": "1m", + "dns-name": "ad8a0be701ea.sn.mynetname.net", + "public-address": "31.173.86.120", + "status": "updated", + "update-time": "true", + "warning": "Router is behind a NAT. Remote connection might not work." +} +*/ + +// https://wiki.mikrotik.com/wiki/Manual:IP/Cloud +func ResourceIpCloud() *schema.Resource { + resSchema := map[string]*schema.Schema{ + MetaResourcePath: PropResourcePath("/ip/cloud"), + MetaId: PropId(Id), + + "ddns_enabled": { + Type: schema.TypeBool, + Optional: true, + Description: "If set to yes, then the device will send an encrypted message to the MikroTik's Cloud " + + "server. The server will then decrypt the message and verify that the sender is an " + + "authentic MikroTik device. If all is OK, then the MikroTik's Cloud server will create a " + + "DDNS record for this device and send a response to the device. Every minute the IP/Cloud " + + "service on the router will check if WAN IP address matches the one sent to MikroTik's " + + "Cloud server and will send encrypted update to cloud server if IP address changes.", + }, + "ddns_update_interval": { + Type: schema.TypeString, + Optional: true, + Default: "none", + Description: "If set DDNS will attempt to connect IP Cloud servers at the set interval. If set to none " + + "it will continue to internally check IP address update and connect to IP Cloud servers " + + "as needed. Useful if IP address used is not on the router itself and thus, cannot be " + + "checked as a value internal to the router.", + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + if old == new { + return true + } + + if old == "none" || new == "none" { + return false + } + + return TimeEquall(k, old, new, d) + }, + }, + "dns_name": { + Type: schema.TypeString, + Computed: true, + Description: "Shows DNS name assigned to the rdevice. Name consists of 12 character serial number " + + "appended by .sn.mynetname.net. This field is visible only after at least one " + + "ddns-request is successfully completed.", + }, + "public_address": { + Type: schema.TypeString, + Computed: true, + Description: "Shows device's IPv4 address that was sent to cloud server. This field is visible only " + + "after at least one IP Cloud request was successfully completed.", + }, + "public_address_ivp6": { + Type: schema.TypeString, + Computed: true, + Description: "Shows device's IPv6 address that was sent to cloud server. This field is visible only " + + "after at least one IP Cloud request was successfully completed.", + }, + "status": { + Type: schema.TypeString, + Computed: true, + Description: "Contains text string that describes current dns-service state. The messages are self " + + "explanatory updating... updated Error: no Internet connection Error: request timed out " + + "Error: REJECTED. Contact MikroTik support Error: internal error - should not happen. One " + + "possible cause is if router runs out of memory.", + }, + "update_time": { + Type: schema.TypeBool, + Optional: true, + Default: true, + Description: "If set to yes then router clock will be set to time, provided by cloud server IF there " + + "is no NTP or SNTP client enabled. If set to no, then IP/Cloud service will never update " + + "the device's clock. If update-time is set to yes, Clock will be updated even when " + + "ddns-enabled is set to no.", + }, + "warning": { + Type: schema.TypeString, + Computed: true, + Description: "Shows a warning message if IP address sent by the device differs from the IP address in " + + "UDP packet header as visible by the MikroTik's Cloud server. Typically this happens if " + + "the device is behind NAT. Example: 'DDNS server received request from IP 123.123.123.123 " + + "but your local IP was 192.168.88.23; DDNS service might not work'", + }, + } + + 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_ip_cloud_test.go b/routeros/resource_ip_cloud_test.go new file mode 100644 index 00000000..d6a78b48 --- /dev/null +++ b/routeros/resource_ip_cloud_test.go @@ -0,0 +1,95 @@ +package routeros + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +const testIpCloudAddress = "routeros_ip_cloud.test" + +func TestAccIpCloudTest_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: testAccIpCloudConfig(0), + Check: resource.ComposeTestCheckFunc( + testAccCheckIpCloudExists(testIpCloudAddress), + resource.TestCheckResourceAttr(testIpCloudAddress, "ddns_update_interval", "none"), + ), + }, + { + Config: testAccIpCloudConfig(1), + Check: resource.ComposeTestCheckFunc( + testAccCheckIpCloudExists(testIpCloudAddress), + resource.TestCheckResourceAttr(testIpCloudAddress, "ddns_update_interval", "15m"), + ), + }, + { + Config: testAccIpCloudConfig(2), + Check: resource.ComposeTestCheckFunc( + testAccCheckIpCloudExists(testIpCloudAddress), + resource.TestCheckResourceAttr(testIpCloudAddress, "ddns_update_interval", "10m"), + ), + }, + { + Config: testAccIpCloudConfig(3), + Check: resource.ComposeTestCheckFunc( + testAccCheckIpCloudExists(testIpCloudAddress), + resource.TestCheckResourceAttr(testIpCloudAddress, "ddns_update_interval", "none"), + ), + }, + }, + }) + }) + } +} + +func testAccCheckIpCloudExists(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 testAccIpCloudConfig(n int) string { + var conf = []string{ + ` +resource "routeros_ip_cloud" "test" { + ddns_enabled = true + ddns_update_interval = "none" +}`, + ` +resource "routeros_ip_cloud" "test" { + ddns_enabled = true + ddns_update_interval = "15m" +}`, + ` +resource "routeros_ip_cloud" "test" { + ddns_enabled = true + ddns_update_interval = "600" +}`, + ` +resource "routeros_ip_cloud" "test" { + ddns_enabled = true +}`, + } + return providerConfig + conf[n] +}