Terraform destroys a resource before updating depending resources

I’m developing a provider and I have an issue with the order of certain events.
I have a resource A and a resource B1. B1 is some kind of group object and A is connected to B1. So A has a dependency on the ID of B1. Now I want to change A to another group. So I create B2 and update A to point to B2. The order Terraform performs these actions is

  1. Create B2
  2. Destroy B1
  3. Update A

The problem that I have is that it is not possible to destroy B1 as long as it is used by A. I would need the order to be:

  1. Create B2
  2. Update A
  3. Destroy B1

I checked on Github for other providers but usually they have ‘ForceNew=True’ on resource A which fixes the problem. But in my case, A is an iscsi volume which can’t be deleted. So A can only be updated and not recreated.

I also don’t really understand why Terraform uses this order. It seems wrong to me that a resource is deleted if it is still referenced by another resource. So I’m not sure actually if this is a bug in Terraform or if there is a way I can fix this in the provider code. Either way I would like to solve this.

I’m also looking for a resource which I can use to reproduce this issue. The resource, preferably an official one which I can run locally, would need an attribute that I can set and the ‘ForceNew’ set to False.

Hi @sanderdescamps,

The default resource replacement ordering in Terraform is delete-then-create, which would remove the B1 instance before any updates could be made. In order to be able to update the dependent resource (A) before destroying it’s dependency, you would need to use the create_before_destroy lifecycle option.

Hi @jbardin,

I’ve tried the create_before_destroy option on both resource A and B. It doesn’t make a difference. Terraform keeps failing when destroying B1 because A still depends on it. As I understand the create_before_destroy option impacts when B2 is created. But it doesn’t have an impact on when A is updated. Or do I miss something?

Can you show an example of the configuration structure you are using here? The create_before_destroy option does have an effect outside of the resource in which it’s defined, because all preceding resource will need to follow the same order, and dependencies will be able to update before the destroy operations. There are some notes here on the subject to help clarify things: terraform/destroying.md at main · hashicorp/terraform · GitHub

Over the last few days I did a lot of testing and debugging. I’ve created a dummy provider to test if the create_before_destroy has the expected behavior on my module. Which it does. Then I went back to the module and started changing it. Not sure why, but at some point it started working. Than I reverted most of my changes and it kept working. In the end the create_before_destroy on B (volume_groups) is the only change that remains. I still don’t know what caused the initial problem. But the create_before_destory did change the order and made sure A is updated before B1 is destroyed.

Bellow you have a simplified version of the final code of the module. ‘A’ are the volumes and ‘B’ are the volume_groups.

variable "volumes" {
  description = <<EOF
Volumes
*  number: number of volumes
*  start_index: index of the first volume
*  volume_group_label: Volumes with the same label will be added to the same volume group
EOF
  default     = []
  type = list(object({
    number             = optional(number)
    start_index        = optional(number)
    volume_group_label = string
  }))
  validation {
    condition     = tobool(var.volumes == null ? true : length(distinct(flatten([for volume_set in var.volumes : range(coalesce(lookup(volume_set, "start_index"), 1), coalesce(lookup(volume_set, "start_index"), 1) + coalesce(lookup(volume_set, "number"), 1))]))) == sum([for volume_set in var.volumes : coalesce(lookup(volume_set, "number"), 1)]))
    error_message = "The defined volumes contain duplicate indexes."
  }
}

locals {
  volumes = var.volumes == null ? [] : flatten([for volume_set in var.volumes : [
    for i in range(coalesce(lookup(volume_set, "start_index"), local.volumes_default["start_index"]), coalesce(lookup(volume_set, "start_index"), local.volumes_default["start_index"]) + coalesce(lookup(volume_set, "number"), local.volumes_default["number"])) : {
      name              = format("vol_%s-%s_%03d", var.namespace, local.platform, i)
      volume_group_name = format("vg-%s-%s-%s", var.namespace, local.platform, lookup(volume_set, "volume_group_label", local.volumes_default["volume_group_label"])) 
  }]])
  volume_group_names = distinct([for v in local.volumes :
    v.volume_group_name
  ])
}

resource "silk_volume_group" "volume_groups" {
  name                 = each.value
  for_each             = toset(local.volume_group_names)
  lifecycle {
    create_before_destroy = true
  }
}

resource "silk_volume" "volumes" {
  name                  = each.value.name
  volume_group_id       = try(silk_volume_group.volume_groups[each.value.volume_group_name].id, null)
  for_each              = { for vol in local.volumes : vol.name => vol }
}

@jbardin thank you for your help