Destroy before create

Is there a way to force destruction of resources before creation?

I have a module that is managing a large list of security group rules. As I have worked on the resource names as a map, I’ve occasionally run into issues where the same security group rule gets created under the new resource name before Terraform destroys the same rule under the old resource name. The openstack API won’t let me create the same security group rule twice, so the API throws an error.

I realize that it’s not feasible for Terraform to be aware of the security group rules themselves to ensure the same rules don’t exist twice at the same time as resource names are changing. Is there a way to tell Terraform to always destroy all resources before creating their replacements? This would be a very helpful lifecycle option to deal with these types of cases.

Failing that, is there another way I can deal with this kind of issue?

Hi @cakemox!

Destroying before creating is Terraform’s default behavior, so I think in your situation there’s something else going on that is making the ordering come out incorrect. If you can share the configuration containing the security group rules and the terraform plan output for an example change that leads to the problem you’ve encountered then I or someone else might be able to spot what’s causing the incorrect action ordering and hopefully suggest a way to correct it.

Thanks, Martin. You are correct. Unfortunately, it seems care must be taken when using a map for my resources. For example, I’m creating security group rules in openstack, and openstack doesn’t allow two identical security group rules to exist at the same time.

Whenever I would change the resource name key in the map I provided to Terraform, Terraform would occasionally try to create a security group rule under the new name before it deleted the old name. Since two identical rules can’t exist at the same time, it fails.

I guess what I am looking for is something to ensure all resources are destroyed first before anything is created, so if a resource is recalculated under a different key in the map, it won’t blow up because the old resource hadn’t been destroyed yet. Like a sort of lifecycle hint like destroy_all_before_create or something.

Here’s an example:

locals {
  sg = "02ef80a2-xxxx"
  rulelist = [
    {
      name           = "api"
      direction      = "ingress"
      ethertype      = "IPv4"
      protocol       = "tcp"
      port_range_min = 443
      port_range_max = 443
      remotes = [
        "192.168.100.101/32",
        "192.168.100.102/32",
        "192.168.100.103/32",
        "192.168.100.104/32",
        "192.168.100.105/32",
        "192.168.100.106/32",
        "192.168.100.107/32",
        "192.168.100.108/32",
        "192.168.100.109/32",
        "192.168.100.110/32",
        "192.168.100.111/32",
        "192.168.100.112/32",
        "192.168.100.113/32",
        "192.168.100.114/32",
        "192.168.100.115/32",
        "192.168.100.116/32",
        "192.168.100.117/32",
      ]
    },
  ]
}

resource "openstack_networking_secgroup_rule_v2" "rule" {
  for_each = {
    for r in flatten(
      [for rule in local.rulelist :
        [for idx, remote in rule.remotes :
          {
            key              = "${rule.name}_${idx}"
            direction        = rule.direction
            ethertype        = rule.ethertype
            protocol         = rule.protocol
            port_range_min   = rule.port_range_min
            port_range_max   = rule.port_range_max
            remote_ip_prefix = remote
          }
        ]
      ]
    ) :
    r.key => r
  }
  security_group_id = local.sg
  direction         = each.value.direction
  ethertype         = each.value.ethertype
  protocol          = each.value.protocol
  port_range_min    = each.value.port_range_min
  port_range_max    = each.value.port_range_max
  remote_ip_prefix  = each.value.remote_ip_prefix
}

If I change the “name” of the rule (from “api” to “https”, for example), the resource names change from api_# to https_#. Some of new resources get created (same security group data) before the old ones are destroyed. It’s a bit of a race: the more rules I am creating, the more overlap in the destroy/create, and the greater odds are I’ll try to create a rule before the old one is destroyed:

Error: Error creating openstack_networking_secgroup_rule_v2: Expected HTTP response code [
] when accessing [POST https://myapi:9696/v2.0/security-group-rules], bu
t got 409 instead
{"NeutronError": {"message": "Security group rule already exists. Rule id is be581a65-xxxx.", "type": "SecurityGroupRuleExists", "detail": ""}}

  on main.tf line 61, in resource "openstack_networking_secgroup_rule_v2" "rule":
  61: resource "openstack_networking_secgroup_rule_v2" "rule" {

Some kind of lifecycle hint to tell Terraform it needs to destroy any old resources before creating any new resources would be very helpful here.

Hi @cakemox!

It looks like the reason for the conflicts is that the keys for openstack_networking_secgroup_rule_v2.rule instances include indexes into your remotes list, and thus adding and removing elements from remotes is subject to a similar problem to what occurs when using count with a list whose ordering might change.

For now, Terraform treats each instance of a resource independently for the purpose of applying changes, and there isn’t a way to force it to consider all of the instance actions together as you describe.

However, there is a different approach that might help mitigate the problem within Terraform’s current capabilities: use remote instead of idx in your instance keys, so that they are identified by their values rather than by their indices:

  for_each = {
    for r in flatten(
      [for rule in local.rulelist :
        [for remote in rule.remotes :
          {
            key              = "${rule.name}:${remote}"
            direction        = rule.direction
            ethertype        = rule.ethertype
            protocol         = rule.protocol
            port_range_min   = rule.port_range_min
            port_range_max   = rule.port_range_max
            remote_ip_prefix = remote
          }
        ]
      ]
    ) :
    r.key => r
  }

This will lead to instance addresses like openstack_networking_secgroup_rule_v2.rule["api:192.168.100.101/32"] rather than openstack_networking_secgroup_rule_v2.rule["api_0"], which should hopefully help Terraform understand better the intent that it should treat additions and removals from remotes by adding or removing corresponding instances, rather than by trying to update the existing instances in-place to realign them with their new indices.

Thanks, Martin. Unfortunately, this does not address the underlying issue. If I change the name, the same issue exists. Ultimately this situation would arise with any resources that must be unique when combined with a map passed to for_each.