'Invalid reference from destroy provisioner after TF upgrade

I keep getting an error when trying tf apply:

Destroy-time provisioners and their connection configurations may only reference attributes of the related resource, via 'self', 'count.index', or 'each.key'.

References to other resources during the destroy phase can cause dependency cycles and interact poorly with create_before_destroy.

Below is my code any idea why this is happening?

resource "azurerm_public_ip" "pip" {
  count = (var.enable_public_ip) && lower(local.is_linux_validation) || lower(local.is_windows_validation) ? 1 : 0
  name  = "${var.vm_name}-public-ip"
  location = data.azurerm_resource_group.vm_rg.location
  resource_group_name = data.azurerm_resource_group.vm_rg.name
  allocation_method   = var.public_ip_allocation_method
  tags  = var.tags
  
  # Deassociate Public IP from NIC before destroying it
  provisioner "local-exec" {
    when = destroy
    command = "az network nic ip-config update --name ${var.vm_name}-ip --resource-group ${data.azurerm_resource_group.vm_rg.name} --nic-name ${var.vm_name}-nic --remove PublicIpAddress"
    on_failure = continue
  }
}


Solution 1:[1]

Because Terraform visits dependencies in reverse order when destroying (so that a child will be destroyed before its parent, for example), by the time Terraform is running destroy-time provisioners all of the other objects that the resource depends on have already been destroyed, and so it isn't valid to refer to them.

As the error message says, the configuration for a when = destroy provisioner may refer only to self (the current instance being destroyed), count.index (the index of the current instance being destroyed), or each.key (the for_each key of the current instance being destroyed).

Your provisioner configuration refers to two other objects which are not valid to use here:

  • var.vm_name
  • data.azurerm_resource_group.vm_rg.name

To proceed you will need to find alternative ways to get this same information by reference to self.

For data.azurerm_resource_group.vm_rg.name it seems like you assigned exactly the same value to this resource's own resource_group_name argument, and so self.resource_group_name would be a drop-in replacement.

You haven't use var.vm_name alone as any of the arguments of this resource, so deriving ${var.vm_name}-ip and ${var.vm_name}-nic will require some more ingenuity. It seems like it should be possible to derive them both from self.name though, since it shares the common prefix:

  • ${trimsuffix(self.name, "-public-ip")}-ip
  • ${trimsuffix(self.name, "-public-ip")}-nic

This relies on the fact that these two arguments share a common prefix with self.name, and thus you can trim off the extra suffix and then append a different one to calculate the name of the corresponding "ip" and "nic".


In Terraform, provisioners are a last resort and so if at all possible it would be better to manage this association between the public IP and its NIC using a Terraform resource block. If the Azure provider doesn't offer a mechanism to do that then it may be worth opening a feature request in the provider's repository to discuss it, so that in future you could hopefully switch to a resource-based approach where Terraform will be able to manage these relationships properly and thus avoid this trickery with reverse-engineering object names.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Martin Atkins