terraform-routeros / terraform-provider-routeros

Terraform Provider for Mikrotik RouterOS
Mozilla Public License 2.0
194 stars 56 forks source link

ip_dhcp_server_lease isn't stored in state. #318

Closed Nornode closed 10 months ago

Nornode commented 10 months ago

Describe the bug resource "routeros_ip_dhcp_server_lease" are not stored in state.

terraform apply will not store the resources in state and will create the static lease over and over again on routeros.

RouterOS: Version 7.12.1 (stable) Board Name RB5009UG+S+ Terraform v1.5.5 - on darwin_arm64 Provider registry.terraform.io/terraform-routeros/routeros v1.27.2

To Reproduce

# Create a static DHCP lease
resource "routeros_ip_dhcp_server_lease" "lan_leases" {
  for_each =  { for key, value in var.static_leases : key => value if value.pool == "LAN" }

  disabled = var.disabled
  dhcp_option = try(each.value.dhcp_option, null)
  dhcp_option_set = try(each.value.dhcp_option_set, null)
  server = routeros_ip_dhcp_server.lan.id
  mac_address = each.value.mac_address
  address = each.value.ip_address
  comment = substr("${substr(each.value.client_name,0,19)} ${var.comment}",0,31)
}

example var.static_lease

{
  "serve1": {
    "ip_address": "192.168.0.1",
    "mac_address": "00:0c:29:RR:DD:SS",
    "pool": "LAN",
    "client_name": "server1"
  },
  "server2": {
    "ip_address": "192.168.11.1",
    "mac_address": "00:0c:29:SS:AA:BB",
    "pool": "LAN2",
    "client_name": "server2"
  },
  "router": {
    "ip_address": "192.168.11.29",
    "mac_address": "44:d9:e7:XX:FF:JJ",
    "pool": "LAN2",
    "client_name": "router"
  },
}

Expected behavior

The above configuration get created on the RouterOS and applies with success. However, re-applying, they are re-created on the router again.

The reservations should be stored in the state. Only changes in configuration should be applied.

vaerh commented 10 months ago

Hi, I was unable to reproduce the problem on CHR 7.11.2. I used the following configuration for testing:

locals {
  static_leases = jsondecode(file("${path.module}/var.static_lease"))
  disabled      = true
  comment       = ""
}

resource "routeros_interface_bridge" "bridge" {
  name = "bridge1"
}

resource "routeros_ip_address" "address" {
  address   = "192.168.0.111/24"
  interface = routeros_interface_bridge.bridge.name
}

resource "routeros_ip_pool" "lan" {
  name   = "LAN"
  ranges = ["192.168.0.1-192.168.0.100"]
}

resource "routeros_ip_dhcp_server" "lan" {
  address_pool = routeros_ip_pool.lan.name
  interface    = routeros_interface_bridge.bridge.name
  name         = "bridge_dhcp_lan"
}

resource "routeros_ip_dhcp_server_lease" "lan_leases" {
  for_each = { for key, value in local.static_leases : key => value if value.pool == "LAN" }

  disabled        = local.disabled
  dhcp_option     = try(each.value.dhcp_option, null)
  dhcp_option_set = try(each.value.dhcp_option_set, null)
  server          = routeros_ip_dhcp_server.lan.id
  mac_address     = each.value.mac_address
  address         = each.value.ip_address
  comment         = substr("${substr(each.value.client_name, 0, 19)} ${local.comment}", 0, 31)
}

var.static_lease

    {
      "server1" : {
        "ip_address" : "192.168.0.1",
        "mac_address" : "00:0c:29:00:01:A0",
        "pool" : "LAN",
        "client_name" : "server1"
      },
      "server2" : {
        "ip_address" : "192.168.11.1",
        "mac_address" : "00:0c:29:00:01:A1",
        "pool" : "LAN2",
        "client_name" : "server2"
      },
      "router" : {
        "ip_address" : "192.168.11.29",
        "mac_address" : "44:d9:e7:00:FF:22",
        "pool" : "LAN2",
        "client_name" : "router"
      }
    }

state file

    {
      "mode": "managed",
      "type": "routeros_ip_dhcp_server_lease",
      "name": "lan_leases",
      "provider": "provider[\"registry.terraform.io/terraform-routeros/routeros\"]",
      "instances": [
        {
          "index_key": "server1",
          "schema_version": 0,
          "attributes": {
            "___id___": null,
            "___path___": null,
            "active_address": null,
            "active_client_id": null,
            "active_hostname": null,
            "active_mac_address": null,
            "active_server": null,
            "address": "192.168.0.1",
            "address_lists": "",
            "agent_circuit_id": null,
            "agent_remote_id": null,
            "allow_dual_stack_queue": null,
            "always_broadcast": null,
            "block_access": false,
            "blocked": false,
            "client_id": null,
            "comment": "server1 ",
            "dhcp_option": "",
            "dhcp_option_set": null,
            "disabled": true,
            "dynamic": false,
            "expires_after": null,
            "host_name": null,
            "id": "*1",
            "insert_queue_before": null,
            "last_seen": "never",
            "lease_time": null,
            "mac_address": "00:0C:29:00:01:A0",
            "radius": "false",
            "rate_limit": null,
            "server": "bridge_dhcp_lan",
            "src_mac_address": null,
            "status": "waiting",
            "use_src_mac": null
          },
          "sensitive_attributes": [],
          "private": "bnVsbA==",
          "dependencies": [
            "routeros_interface_bridge.bridge",
            "routeros_ip_dhcp_server.lan",
            "routeros_ip_pool.lan"
          ]
        }
      ]
    },

And it's better to use the server indication in this way:

server          = routeros_ip_dhcp_server.lan.name
Nornode commented 10 months ago

I'm very sorry. I was about to close this issue as I figured out the trouble causing this bahaviour, and it was not the resource.

The full picture: My templates are keept dry with Terragrunt. I transitioned to terragrunt and configured it to remove all cache files after ["plan", "apply", "destroy"]. By mistake I keept the old state file in the root of terraform folder so this static lease was the only resources created (terragrunt read the old state file) and wrote the newly created resources to a statefile in the cache directory which was removed on action completion.

I managed to resolve it by

  1. removing the old statefile in terraform template folder
  2. configuring a remote state file storage over GCS.

I still think it's strange that the RouterOS let's is create several static leases for the same mac. šŸ¤”

And it's better to use the server indication in this way:

server = routeros_ip_dhcp_server.lan.name

I guess that's your flavour, I don't agree. The id attribute is imutable for the resource so it should be a better reference. I realised that the there were a lot of changes requested on re-apply id => name which is fixed with a lifecycle block:

  lifecycle {
    ignore_changes = [server]
  }

I guess that's my flavour, is it better? šŸ¤·ā€ā™‚ļø

vaerh commented 10 months ago

That's great. Yes, you have correctly understood the problem with constantly changing id => name. It is good that it is so easily solved in Terragrunt.

Nornode commented 10 months ago

Past: The lifecycle argument is native to Terraform.. šŸ‘