terraform-provider-incus icon indicating copy to clipboard operation
terraform-provider-incus copied to clipboard

Properties passed as null cause error (regression w.r.t. lxd provider)

Open johnlane opened this issue 2 weeks ago • 4 comments

I’ve been using a try with null as a default to set parameters in the LXD Terraform Provider for quite some time. This is based on the premise that there is no difference between omitting a Terraform provider argument or setting it explicitly to null. I've used this technique in modules where I want some parameters to be optional.

Since migrating instances to Incus with the Incus Terraform Provider, similar configuration now triggers an error message. I am using v1.0 of the Incus Terraform Provider.

This exaple illustrates the problem:

variable "bridge_hwaddr" {
  type = string
  default = null
# default = "be:ef:ca:fe:ba:be"
}

resource "lxd_container" "test" {
  name             = "test-resource-lxc"
 #image            = "images:busybox/1.36.1"
  image            = "busybox/1.36.1"
  wait_for_network = false

  device {
    name = "eth0"
    type = "nic"
    properties = {
      nictype = "bridged"
      parent  = "br0"
      hwaddr = try(var.bridge_hwaddr, null)    # if this becomes null then OK
    }
  }

}

resource "incus_instance" "test" {
  name             = "test-resource-incus"
  image            = "images:busybox/1.36.1"

  device {
    name = "eth0"
    type = "nic"
    properties = {
      nictype = "bridged"
      parent  = "br0"
      hwaddr = try(var.bridge_hwaddr, null)    # if this becomes null then ERROR!!!
    }
  }

}

Here is an example of a plan to create the resources

Terraform will perform the following actions:

  # incus_instance.test will be created
  + resource "incus_instance" "test" {
      + architecture = (known after apply)
      + config       = {}
      + ephemeral    = false
      + image        = "images:busybox/1.36.1"
      + ipv4_address = (known after apply)
      + ipv6_address = (known after apply)
      + mac_address  = (known after apply)
      + name         = "test-resource-incus"
      + profiles     = [
          + "default",
        ]
      + running      = true
      + status       = (known after apply)
      + target       = (known after apply)
      + type         = "container"

      + device {
          + name       = "eth0"
          + properties = {
              + "hwaddr"  = null
              + "nictype" = "bridged"
              + "parent"  = "br0"
            }
          + type       = "nic"
        }
    }

  # lxd_container.test will be created
  + resource "lxd_container" "test" {
      + ephemeral        = false
      + id               = (known after apply)
      + image            = "busybox/1.36.1"
      + ip_address       = (known after apply)
      + ipv4_address     = (known after apply)
      + ipv6_address     = (known after apply)
      + mac_address      = (known after apply)
      + name             = "test-resource-lxd"
      + privileged       = false
      + profiles         = (known after apply)
      + start_container  = true
      + status           = (known after apply)
      + target           = (known after apply)
      + type             = (known after apply)
      + wait_for_network = false

      + device {
          + name       = "eth0"
          + properties = {
              + "nictype" = "bridged"
              + "parent"  = "br0"
            }
          + type       = "nic"
        }
    }

Plan: 2 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

lxd_container.test: Creating...
incus_instance.test: Creating...
lxd_container.test: Still creating... [10s elapsed]
lxd_container.test: Creation complete after 11s [id=test-resource-lxd]
╷
│ Error: Provider produced inconsistent result after apply
│
│ When applying changes to incus_instance.test, provider "provider[\"registry.terraform.io/lxc/incus\"]"
│ produced an unexpected new value: .device: planned set element
│ cty.ObjectVal(map[string]cty.Value{"name":cty.StringVal("eth0"),
│ "properties":cty.MapVal(map[string]cty.Value{"hwaddr":cty.NullVal(cty.String),
│ "nictype":cty.StringVal("bridged"), "parent":cty.StringVal("br0")}),
│ "type":cty.StringVal("nic")}) does not correlate with any element in actual.
│
│ This is a bug in the provider, which should be reported in the provider's own issue tracker.

Despite the error, both instances are created.

Notice how + "hwaddr" = null is in the plan for Incus but is absent for LXD.

The error does not happen if the null is avoided (by setting the variable to a valid value). Changing the variable to null after a successful apply yields a different error message. Here is an example of the error message

│ Error: Value Conversion Error
│
│   with incus_instance.test,
│   on resource.tf line 26, in resource "incus_instance" "test":
│   26: resource "incus_instance" "test" {
│
│ An unexpected error was encountered trying to build a value. This is always an error in the provider.
│ Please report the following to the provider developer:
│
│ Received null value, however the target type cannot handle null values. Use the corresponding `types`
│ package type, a pointer type or a custom type that handles null values.
│
│ Path: ["hwaddr"]
│ Target Type: string
│ Suggested `types` Type: basetypes.StringValue
│ Suggested Pointer Type: *string

johnlane avatar Nov 22 '25 17:11 johnlane

Not sure I would call this a regression, but rather a feature. So your idea is that when you pass the value null then the config or properties entry should be ignored?

maveonair avatar Nov 22 '25 20:11 maveonair

I called it a regression because it worked with the lxd provider and it does not work with the incus provider.

The idea is that a property that is given as null is treated the same as if it were not given at all.

It's visible in the plan output above where the hwaddr is omitted from the lxd plan (because it's null) but is present in the incus plan .

johnlane avatar Nov 22 '25 20:11 johnlane

Sure, but we forked a year ago and we have many new features that the LXD provider doesn’t have, and vice versa.

maveonair avatar Nov 22 '25 21:11 maveonair

I just checked the LXD providers that I was using where this feature worked and I have between 1.41 (Dec 12m 2020) and 1.73 (Nov 2, 2022). The tests above used LXD provider 1.73. All of these pre-date the Incus fork in 2023 so I assumed that existing behaviours would have transferred.

Quoting the Terraform documentation:

If you set an argument of a resource to null, Terraform behaves as though you had completely omitted it

If this behaviour can be reinstated that would be great, or is there another way that I should do this? I thought this was a pretty standard Terraform pattern.

johnlane avatar Nov 23 '25 09:11 johnlane

@johnlane I just realized that you must use an outdated version of the LXD Terraform provider because lxd_container is not existing anymore since v2.0, it's now called lxd_instance.

Could you do me a favor an upgrade to the newest LXD Terraform provider version to see if you get to the same result?

maveonair avatar Nov 28 '25 16:11 maveonair

I just did a quick test (LXD provider 2.6.1) and it also fails with a null value.

resource "lxd_instance" "test" {
  name             = "test-resource-lxd"
  image            = "images:busybox/1.36.1
  wait_for_network = false

  device {
    name = "eth0"
    type = "nic"
    properties = {
      nictype = "bridged"
      parent  = "br0"
      hwaddr = try(var.bridge_hwaddr, null)    # if this becomes null then ERROR!!!
    }
  }

}
 Error: Value Conversion Error
│
│   with lxd_instance.test,
│   on resource.tf line 8, in resource "lxd_instance" "test":
│    8: resource "lxd_instance" "test" {
│
│ An unexpected error was encountered trying to build a value. This is always an error in the provider.
│ Please report the following to the provider developer:
│
│ Received null value, however the target type cannot handle null values. Use the corresponding `types`
│ package type, a pointer type or a custom type that handles null values.
│
│ Path: ["hwaddr"]
│ Target Type: string
│ Suggested `types` Type: basetypes.StringValue
│ Suggested Pointer Type: *string

I don't have much LXD infra left.

johnlane avatar Nov 29 '25 10:11 johnlane

Thanks a lot for validating this, as I have expected this behaviour to see as well while going through the LXD Terraform provider code.

maveonair avatar Dec 01 '25 13:12 maveonair