Skip to content

Terraform vcd_network_routed_v2 with cidrhost() Calculated IPv6 Address Format Issue - "forces replacement"

After a long time of missing IPv6 Support in the Terraform Provider for VMware Cloud Director, with the release of v3.10.0 IPv6 Dual-Stack support for routed networks is finally there. Unfortunately, when you want to use the Terraform native cidrhost() function, you might run into an issue that is caused by the different formats in which you can write IPv6 addresses. The format in which the IP Address is calculated differs from the format that the VCD API returns which forces Terraform to replace the resource.

There are 3 formats to write a valid IPv6 address. Leading zeros in each 16-bit block can be suppressed and the longest sequence of consecutive all-zero blocks can be replaced with two colons (::). Given that, the following three notations represent the same IP address, which I want to configure as gateway:

  • 2001:0db8:0000:0000:0000:0000:0000:0001/64
  • 2001:db8:0:0:0:0:0:1/64
  • 2001:db8::1/64

Despite being an identical address, terraform denotes the gateway address as different and wants to reconfigure it. Even worse is the fact that you can't reconfigure a network's IPv6 address and thus it destroys and recreates it, which it can't do if there are Virtual Machines.

So, why does that happen? VMware Cloud Director API always uses the 2001:db8:0:0:0:0:0:1 format to represent an IPv6 Address. You can configure it in the format of your choice, but it will always be saved in the same format. Contrary to that, the cidrhost() function always uses the short format:

Workaround

If you do not have the requirement to dynamically calculate the gateway address, the solution is quite simple: Just stick to the default format that VCD API wants. To use cidrhost() you do not have many options as a provider user. You can neither change the VCD API behavior, nor cidrhost() has an option to change the format.

Workaround 1 - lifecycle { ignore_changes }
This is one of the features I don't enjoy using at all but as a last resort, sometimes you have to make use it. You can instruct Terraform to ignore specific attribute changes after the initial creation with a lifecycle Meta-Argument. In that specific case, it just works and is not that bad. You already can't change the IPv6 address on an existing network so it's no big deal that it is ignored in Terraform.

resource "vcd_network_routed_v2" "ipv6_dualstack" {
  name                    = "dual-stack"
  edge_gateway_id         = data.vcd_nsxt_edgegateway.t1.id
  gateway                 = "192.168.1.1"
  prefix_length           = 24
  dual_stack_enabled      = true
  secondary_gateway       = cidrhost(var.ipv6_cidr, 1) # ipv6_cidr = "2001:db8::/64"
  secondary_prefix_length = split("/", var.ipv6_cidr)[1]
  lifecycle {
    ignore_changes = [
      secondary_gateway,
      secondary_prefix_length
    ]
  }
}

Workaround 2 - String Magic

Another workaround is to solve it by fixing the address format with string manipulation. You have to figure out how many zero blocks are omitted and add them manually. This can be done with native Terraform functions:

  1. Count how many ":" the address has by splitting and counting the result.
    > length(split(":", cidrhost(var.ipv6_cidr, 1))
    4
  2. Subtract the result from 9 (An IPv6 address should have 7 colons + the two colons that need to get removed)
    > 9 - (length(split(":", cidrhost(var.ipv6_cidr, 1))))
    5
  3. Repeat the string "0:" x (result from 2.) times.
    > join("", [for _ in range(9 - (length(split(":", cidrhost(var.ipv6_cidr, 1))))) : "0:"])
    > ":0:0:0:0:0:"
  4. Replace "::" with a single ":" and the result from step 3
    > replace(cidrhost(var.ipv6_cidr, 1), "::", ":${join("", [for _ in range(9 - (length(split(":", cidrhost(var.ipv6_cidr, 1))))) : "0:"])}")2001:db8:0:0:0:0:0:1"
    > "2001:db8:0:0:0:0:0:1"

Example:

resource "vcd_network_routed_v2" "ipv6_dualstack" {
  name                    = "dual-stack"
  edge_gateway_id         = data.vcd_nsxt_edgegateway.t1.id
  gateway                 = "192.168.1.1"
  prefix_length           = 24
  dual_stack_enabled      = true
  secondary_gateway       = replace(cidrhost(var.ipv6_cidr, 1), "::", ":${join("", [for _ in range(9 - (length(split(":", cidrhost(var.ipv6_cidr, 1))))) : "0:"])}")
  secondary_prefix_length = split("/", var.ipv6_cidr)[1]
}

 

Let me know if you have a better solution...

Leave a Reply

Your email address will not be published. Required fields are marked *