[Solved] Need to run 'terraform apply' twice to get a proper Output

This topic is the copy of https://github.com/dmacvicar/terraform-provider-libvirt/issues/745

I have

Terraform v0.12.24
+ provider.libvirt (unversioned)
+ provider.local v1.4.0
+ provider.template v2.1.2

I use a template to generate an ansible’s inventory from the terraform.

    locals {
      ips = libvirt_domain.k8s.*.network_interface.0.addresses
      names = libvirt_domain.k8s.*.network_interface.0.hostname
      inventory = templatefile("${path.module}/inventory.tpl", 
      {
	     nodes = zipmap(local.names,
	       flatten(local.ips))
      })
    }

    # generate inventory file for Ansible
    resource "local_file" "inventory" {
      content = local.inventory
      filename = "${path.module}/../kubespray/inventory/mycluster/inventory.ini"
      depends_on = [libvirt_domain.k8s]
    }

And it works only if I run terraform apply twice. After first run (I use an output to debug) in the local.inventory I got only

nodes = {
  "" = "10.0.1.95"
}

and after second I see the proper result:

nodes = {
  "k8s0" = "10.0.1.253"
  "k8s1" = "10.0.1.190"
  "k8s2" = "10.0.1.95"
}

Interesting that the problem reveals only if I use zipmap and flatten functions. (I need them to get proper data for a template). If I use the regular value as

     output "ips" {
       value = libvirt_domain.k8s.*.network_interface.0.addresses
     }

It provides all 3 IPs from the first run. I’m very confused about that behaviour.

Hi @igajsin,

I’m pretty confused about this behavior too!

Based on how the zipmap function works, it seems to me that the libvirt_domain resource type’s implementation is, on your first apply, returning an empty string in all of the hostname attributes, which then causes all of the keys in your zipmap call to be "" and so (because map keys must be unique) only the last one in the sequence is retained.

libvirt is not a provider I’m familiar with, so I can’t really say for sure that it’s behaving in this way or explain why. From looking quickly at its source code I see that there is some sense of “pending addresses”, and so maybe the hostname is allocated asynchronously and so not available until after the first terraform apply has completed?

I was going to suggest asking about this in the provider’s own repository, but I see you already opened an issue about it:

I think a way to confirm or disprove my theory above would be to run terraform apply with full trace logging enabled, by setting the environment variable TF_LOG=trace. With that set, we should hopefully see some log lines printed by the codepath I linked to above:

[INFO] Finally adding IP/MAC/host=10.0.1.95/XX-XX-XX-XX-XX-XX/k8s2

I’m curious to learn if you can see those log lines on your first apply (the one where the zipmap result seemed incomplete) and, if so, whether the hostname portion at the end is set as you expect. If my theory above were true then I think the log line would be missing the hostname at the end, so it would look more like this:

[INFO] Finally adding IP/MAC/host=10.0.1.95/XX-XX-XX-XX-XX-XX/

(notice that the hostname at the end is empty in this example)

If it does turn out that the hostname is coming back as empty then that indicates a bug in the provider. If not, we can investigate other possibilities.

The valid point. I’ve run the

     output "hostnames" {
       value = libvirt_domain.k8s.*.network_interface.0.hostname
     }

And got

hostnames = [
  "",
  "",
  "",
]

And when I set TF_LOG=trace I’ve got the reason.

...
[INFO] Finally adding IP/MAC/host=10.0.1.218/52:54:00:2E:C4:29/k8s1 <-- so far so good
...
2020/05/30 09:37:24 [WARN] Provider "registry.terraform.io/-/libvirt" produced an unexpected new value for libvirt_domain.k8s[1], but we are tolerating it because it is using the legacy plugin SDK.
    The following problems may be the cause of any confusing errors from downstream operations:
...
      - .network_interface[0].hostname: was cty.StringVal("k8s1"), but now cty.StringVal("")
...

I’ll go to the libvirt-provider plugin’s repo to ask what to do with that. Thanks for you help.

To summary the result of my investigation:

  1. There was a problem hostname is not set if dhcp is used for assigning addresses in the terraform-provider-libvirt plugin.
  2. Because of the wrong treatment of hostnames when dhcp, instead of expected {hostname0=ip0, hostname1=ip1, ...} I got {""=ip0, ""=ip1, ""=ip2} structure, that obviously can’t work with zipmap.
  3. After updating the plugin to 0.6.2+git.1585292411.8cbe9ad0 the original issue has gone and my code works too.
1 Like

Great! Thanks for following up @igajsin, and I’m glad you were able to get things working by upgrading to a newer version of the plugin.